@flighthq/effects-canvas 0.4.0-next.1416.ef4fae7 → 0.4.0-next.1487.e25e95d

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.
@@ -0,0 +1,5 @@
1
+ import type { CanvasRenderEffectRunner, CanvasRenderState, CanvasRenderTarget, LensDistortionEffect } from '@flighthq/types/contract';
2
+ export declare function applyLensDistortionEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, effect: Readonly<LensDistortionEffect>): void;
3
+ export declare const defaultCanvasLensDistortionEffectRunner: CanvasRenderEffectRunner;
4
+ export declare function registerCanvasLensDistortionEffect(state: CanvasRenderState): void;
5
+ //# sourceMappingURL=canvasLensDistortionEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasLensDistortionEffect.d.ts","sourceRoot":"","sources":["../src/canvasLensDistortionEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAiBlC,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GACrC,IAAI,CAqDN;AAED,eAAO,MAAM,uCAAuC,EAAE,wBAErD,CAAC;AAEF,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEjF"}
@@ -0,0 +1,69 @@
1
+ import { drawCanvasImageDataPass } from './canvasEffectCompositing';
2
+ import { registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
3
+ // Lens distortion (REAL): remap uv by the same radial polynomial the Gl/Wgpu recipe uses —
4
+ // `centered = (uv - 0.5) / scale; distorted = centered * (1 + amount * dot(centered, centered)) + 0.5` —
5
+ // sampling the source at the distorted coordinate and writing opaque black where it leaves the frame.
6
+ //
7
+ // ★ THIS IS A RESAMPLE, NOT A COLOUR OP, which is why it needs the ImageData pass rather than a CSS
8
+ // filter chain. It reads the whole source once into a snapshot and writes a fresh buffer, because the
9
+ // destination pixel at (x, y) reads a SOURCE pixel somewhere else entirely: transforming in place would
10
+ // feed already-distorted pixels back into later samples.
11
+ //
12
+ // ★ SAMPLING IS BILINEAR, MATCHING THE GPU'S `linear` filter. Nearest-neighbour would land within a
13
+ // pixel of the right answer everywhere and still look wrong along every edge in the picture, which is
14
+ // the difference between "the parameter is observable" and "the backends agree".
15
+ export function applyLensDistortionEffectToCanvas(source, dest, effect) {
16
+ const amount = effect.amount ?? 0.2;
17
+ const scale = effect.scale ?? 1;
18
+ drawCanvasImageDataPass(dest, source, (data, pixelCount) => {
19
+ const width = source.width;
20
+ const height = Math.max(1, Math.round(pixelCount / Math.max(1, width)));
21
+ const snapshot = new Uint8ClampedArray(data);
22
+ for (let y = 0; y < height; y++) {
23
+ for (let x = 0; x < width; x++) {
24
+ const out = (y * width + x) * 4;
25
+ // Pixel CENTRES, matching how the GPU rasterizer hands v_texCoord to the fragment shader; using
26
+ // x/width instead would sample half a texel off across the whole frame.
27
+ const centeredX = ((x + 0.5) / width - 0.5) / scale;
28
+ const centeredY = ((y + 0.5) / height - 0.5) / scale;
29
+ const radiusSquared = centeredX * centeredX + centeredY * centeredY;
30
+ const factor = 1 + amount * radiusSquared;
31
+ const u = centeredX * factor + 0.5;
32
+ const v = centeredY * factor + 0.5;
33
+ if (u < 0 || u > 1 || v < 0 || v > 1) {
34
+ data[out] = 0;
35
+ data[out + 1] = 0;
36
+ data[out + 2] = 0;
37
+ data[out + 3] = 255;
38
+ continue;
39
+ }
40
+ const sampleX = u * width - 0.5;
41
+ const sampleY = v * height - 0.5;
42
+ const x0 = Math.floor(sampleX);
43
+ const y0 = Math.floor(sampleY);
44
+ const fx = sampleX - x0;
45
+ const fy = sampleY - y0;
46
+ const x1 = Math.min(width - 1, Math.max(0, x0 + 1));
47
+ const y1 = Math.min(height - 1, Math.max(0, y0 + 1));
48
+ const cx0 = Math.min(width - 1, Math.max(0, x0));
49
+ const cy0 = Math.min(height - 1, Math.max(0, y0));
50
+ const topLeft = (cy0 * width + cx0) * 4;
51
+ const topRight = (cy0 * width + x1) * 4;
52
+ const bottomLeft = (y1 * width + cx0) * 4;
53
+ const bottomRight = (y1 * width + x1) * 4;
54
+ for (let channel = 0; channel < 4; channel++) {
55
+ const top = snapshot[topLeft + channel] * (1 - fx) + snapshot[topRight + channel] * fx;
56
+ const bottom = snapshot[bottomLeft + channel] * (1 - fx) + snapshot[bottomRight + channel] * fx;
57
+ data[out + channel] = Math.round(top * (1 - fy) + bottom * fy);
58
+ }
59
+ }
60
+ }
61
+ });
62
+ }
63
+ export const defaultCanvasLensDistortionEffectRunner = (ctx, effect) => {
64
+ applyLensDistortionEffectToCanvas(ctx.source, ctx.dest, effect);
65
+ };
66
+ export function registerCanvasLensDistortionEffect(state) {
67
+ registerCanvasRenderEffect(state, 'LensDistortionEffect', defaultCanvasLensDistortionEffectRunner);
68
+ }
69
+ //# sourceMappingURL=canvasLensDistortionEffect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasLensDistortionEffect.js","sourceRoot":"","sources":["../src/canvasLensDistortionEffect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CanvasRenderEffectRunner, CanvasRenderState, CanvasRenderTarget, PosterizeEffect } from '@flighthq/types/contract';
2
+ export declare function applyPosterizeEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, effect: Readonly<PosterizeEffect>): void;
3
+ export declare const defaultCanvasPosterizeEffectRunner: CanvasRenderEffectRunner;
4
+ export declare function registerCanvasPosterizeEffect(state: CanvasRenderState): void;
5
+ //# sourceMappingURL=canvasPosterizeEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasPosterizeEffect.d.ts","sourceRoot":"","sources":["../src/canvasPosterizeEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAkBlC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,GAChC,IAAI,CAYN;AAED,eAAO,MAAM,kCAAkC,EAAE,wBAEhD,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE5E"}
@@ -0,0 +1,35 @@
1
+ import { drawCanvasImageDataPass } from './canvasEffectCompositing';
2
+ import { registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
3
+ // Posterize (REAL): floor each channel to `levels` discrete steps, matching the Gl/Wgpu recipe
4
+ // `floor(c * levels) / (levels - 1)` exactly rather than approximating it.
5
+ //
6
+ // ★ THE QUANTISATION IS DONE PER PIXEL, NOT THROUGH A CSS FILTER, because no CSS filter expresses a
7
+ // step function. `drawCanvasImageDataPass` is the package's existing primitive for exactly this case,
8
+ // and its presence is the reason this cell was implementable at all: the scene had been declared a
9
+ // backend CONTROL on the belief that Canvas 2D can only reach what `ctx.filter` can spell.
10
+ //
11
+ // ★ THE 255-DOMAIN ROUNDING IS THE WHOLE DIFFICULTY. The GPU works in floats and writes an 8-bit result
12
+ // once, at the end. Doing the same arithmetic on bytes rounds twice — once reading the byte, once
13
+ // writing it — and a step function amplifies a half-level rounding difference into a whole band moving.
14
+ // So the byte is converted to the float domain, quantised there with the identical expression, and only
15
+ // then rounded back, with the same clamp the shader applies.
16
+ export function applyPosterizeEffectToCanvas(source, dest, effect) {
17
+ const levels = Math.max(2, effect.levels ?? 8);
18
+ drawCanvasImageDataPass(dest, source, (data, pixelCount) => {
19
+ for (let pixel = 0; pixel < pixelCount; pixel++) {
20
+ const at = pixel * 4;
21
+ for (let channel = 0; channel < 3; channel++) {
22
+ const value = data[at + channel] / 255;
23
+ const quantised = Math.floor(value * levels) / (levels - 1);
24
+ data[at + channel] = Math.round(Math.max(0, Math.min(1, quantised)) * 255);
25
+ }
26
+ }
27
+ });
28
+ }
29
+ export const defaultCanvasPosterizeEffectRunner = (ctx, effect) => {
30
+ applyPosterizeEffectToCanvas(ctx.source, ctx.dest, effect);
31
+ };
32
+ export function registerCanvasPosterizeEffect(state) {
33
+ registerCanvasRenderEffect(state, 'PosterizeEffect', defaultCanvasPosterizeEffectRunner);
34
+ }
35
+ //# sourceMappingURL=canvasPosterizeEffect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasPosterizeEffect.js","sourceRoot":"","sources":["../src/canvasPosterizeEffect.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,+FAA+F;AAC/F,2EAA2E;AAC3E,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,mGAAmG;AACnG,2FAA2F;AAC3F,EAAE;AACF,wGAAwG;AACxG,kGAAkG;AAClG,wGAAwG;AACxG,wGAAwG;AACxG,6DAA6D;AAC7D,MAAM,UAAU,4BAA4B,CAC1C,MAAoC,EACpC,IAAkC,EAClC,MAAiC;IAEjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAC/C,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE;QACzD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC;YAChD,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC;YACrB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;gBAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,GAAG,OAAO,CAAE,GAAG,GAAG,CAAC;gBACxC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC5D,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,kCAAkC,GAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IAC1F,4BAA4B,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAyB,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAAC,KAAwB;IACpE,0BAA0B,CAAC,KAAK,EAAE,iBAAiB,EAAE,kCAAkC,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { CanvasRenderEffectRunner, CanvasRenderState, CanvasRenderTarget, TiltShiftEffect } from '@flighthq/types/contract';
2
+ export declare function applyTiltShiftEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, effect: Readonly<TiltShiftEffect>): void;
3
+ export declare const defaultCanvasTiltShiftEffectRunner: CanvasRenderEffectRunner;
4
+ export declare function registerCanvasTiltShiftEffect(state: CanvasRenderState): void;
5
+ //# sourceMappingURL=canvasTiltShiftEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasTiltShiftEffect.d.ts","sourceRoot":"","sources":["../src/canvasTiltShiftEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAiBlC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,GAChC,IAAI,CAiDN;AAED,eAAO,MAAM,kCAAkC,EAAE,wBAEhD,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE5E"}
@@ -0,0 +1,67 @@
1
+ import { drawCanvasImageDataPass } from './canvasEffectCompositing';
2
+ import { registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
3
+ // Tilt shift (REAL): keep a horizontal focus band sharp and blur above and below it, matching the
4
+ // Gl/Wgpu recipe tap for tap — seven uniformly weighted vertical samples spaced `radius` pixels apart,
5
+ // where `radius = smoothstep(width/2, width/2 + width, |y - center|) * blur`.
6
+ //
7
+ // ★ THE TAP COUNT AND WEIGHTING ARE PART OF THE PICTURE, not an implementation detail. A Gaussian of
8
+ // the same nominal radius would be a defensible blur and a different image; seven equal taps is what
9
+ // the other two backends draw, so it is what agreement means here.
10
+ //
11
+ // ★ `center` IS MEASURED DOWN FROM THE TOP EDGE (see TiltShiftEffect). Canvas ImageData is already
12
+ // top-down, so this runner passes the value straight through where the Gl runner must flip it. That
13
+ // asymmetry is the convention working, not a discrepancy — and a centred band hides it completely,
14
+ // because |y - 0.5| is symmetric, so only an off-centre band can show it going wrong.
15
+ export function applyTiltShiftEffectToCanvas(source, dest, effect) {
16
+ const center = effect.center ?? 0.5;
17
+ const bandWidth = effect.width ?? 0.3;
18
+ const blur = effect.blur ?? 4;
19
+ drawCanvasImageDataPass(dest, source, (data, pixelCount) => {
20
+ const width = source.width;
21
+ const height = Math.max(1, Math.round(pixelCount / Math.max(1, width)));
22
+ // The taps read the ORIGINAL image. Blurring in place would feed each row's own blurred result
23
+ // into the next row's taps, which is a different (and direction-dependent) operator.
24
+ const snapshot = new Uint8ClampedArray(data);
25
+ const edge = bandWidth * 0.5;
26
+ for (let y = 0; y < height; y++) {
27
+ const distance = Math.abs((y + 0.5) / height - center);
28
+ const ramp = Math.max(0, Math.min(1, (distance - edge) / Math.max(1e-6, bandWidth)));
29
+ const radius = ramp * ramp * (3 - 2 * ramp) * blur;
30
+ for (let x = 0; x < width; x++) {
31
+ const out = (y * width + x) * 4;
32
+ let red = 0;
33
+ let green = 0;
34
+ let blue = 0;
35
+ let alpha = 0;
36
+ for (let tap = -TAP_REACH; tap <= TAP_REACH; tap++) {
37
+ // The GPU offsets by `tap * radius * texel`, which lands on a fractional row; its linear
38
+ // sampler then interpolates. Rounding to the nearest row instead would quantise the ramp
39
+ // into visible bands wherever radius passes a half-pixel.
40
+ const sampleY = y + tap * radius;
41
+ const low = Math.max(0, Math.min(height - 1, Math.floor(sampleY)));
42
+ const high = Math.max(0, Math.min(height - 1, low + 1));
43
+ const blend = Math.max(0, Math.min(1, sampleY - Math.floor(sampleY)));
44
+ const lowAt = (low * width + x) * 4;
45
+ const highAt = (high * width + x) * 4;
46
+ red += snapshot[lowAt] * (1 - blend) + snapshot[highAt] * blend;
47
+ green += snapshot[lowAt + 1] * (1 - blend) + snapshot[highAt + 1] * blend;
48
+ blue += snapshot[lowAt + 2] * (1 - blend) + snapshot[highAt + 2] * blend;
49
+ alpha += snapshot[lowAt + 3] * (1 - blend) + snapshot[highAt + 3] * blend;
50
+ }
51
+ data[out] = Math.round(red / TAP_COUNT);
52
+ data[out + 1] = Math.round(green / TAP_COUNT);
53
+ data[out + 2] = Math.round(blue / TAP_COUNT);
54
+ data[out + 3] = Math.round(alpha / TAP_COUNT);
55
+ }
56
+ }
57
+ });
58
+ }
59
+ export const defaultCanvasTiltShiftEffectRunner = (ctx, effect) => {
60
+ applyTiltShiftEffectToCanvas(ctx.source, ctx.dest, effect);
61
+ };
62
+ export function registerCanvasTiltShiftEffect(state) {
63
+ registerCanvasRenderEffect(state, 'TiltShiftEffect', defaultCanvasTiltShiftEffectRunner);
64
+ }
65
+ const TAP_REACH = 3;
66
+ const TAP_COUNT = TAP_REACH * 2 + 1;
67
+ //# sourceMappingURL=canvasTiltShiftEffect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvasTiltShiftEffect.js","sourceRoot":"","sources":["../src/canvasTiltShiftEffect.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"canvasVignetteEffect.d.ts","sourceRoot":"","sources":["../src/canvasVignetteEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAQlC,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,GAC/B,IAAI,CAoCN;AAED,eAAO,MAAM,iCAAiC,EAAE,wBAE/C,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE3E"}
1
+ {"version":3,"file":"canvasVignetteEffect.d.ts","sourceRoot":"","sources":["../src/canvasVignetteEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAgBlC,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,GAC/B,IAAI,CA+CN;AAED,eAAO,MAAM,iCAAiC,EAAE,wBAE/C,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE3E"}
@@ -1,8 +1,15 @@
1
1
  import { drawCanvasEffectPass } from './canvasEffectCompositing';
2
2
  import { registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
3
- // Vignette (REAL): draw the scene, then overlay a radial gradient — transparent inside `radius`,
4
- // ramping to the vignette color over `softness` toward the corners — using the 'multiply' composite op
5
- // so the edges darken toward the color. Intensity and the color's alpha scale the darkening.
3
+ // Vignette (REAL): draw the scene, then overlay a radial gradient with the 'multiply' composite op so
4
+ // the edges darken toward the vignette color. `intensity` and the color's alpha scale the darkening.
5
+ //
6
+ // ★ THE TWO PUBLIC PARAMETERS MUST BOTH BE OBSERVABLE, AND ONCE THEY WERE NOT. An earlier revision ran
7
+ // the gradient from the ramp start all the way to the FRAME CORNER, so full darkness arrived at the
8
+ // corner rather than at `radius`. That made the visible ramp (1 - (radius - softness)) wide instead of
9
+ // `softness` wide, which meant `softness` alone set the falloff and `radius` stopped being independently
10
+ // observable at all: two parameters collapsed into one, with a picture darker than either asks for. Full
11
+ // dark belongs at `radius`; the gradient's own clamp keeps everything beyond it dark.
12
+ const RAMP_STOPS = 16;
6
13
  export function applyVignetteEffectToCanvas(source, dest, effect) {
7
14
  const intensity = effect.intensity ?? 1;
8
15
  const radius = effect.radius ?? 0.75;
@@ -14,26 +21,36 @@ export function applyVignetteEffectToCanvas(source, dest, effect) {
14
21
  const ctx = dest.context;
15
22
  const w = dest.width;
16
23
  const h = dest.height;
17
- const cx = w * 0.5;
18
- const cy = h * 0.5;
19
- // The vignette math measures distance to the corner as 1.0 (matching the Gl recipe's diagonal
20
- // normalization), so the gradient's outer radius is half the diagonal.
21
- const outer = Math.sqrt(cx * cx + cy * cy);
22
- const inner = Math.max(0, Math.min(radius, 1)) * outer;
23
- const ramp = Math.max(0, inner - softness * outer);
24
- const gradient = ctx.createRadialGradient(cx, cy, ramp, cx, cy, outer);
25
- // Opaque-at-center color string with alpha replaced by the darken amount at the rim.
26
- const r = (color >>> 24) & 0xff;
27
- const g = (color >>> 16) & 0xff;
28
- const b = (color >>> 8) & 0xff;
29
- gradient.addColorStop(0, `rgba(${r},${g},${b},0)`);
30
- gradient.addColorStop(1, `rgba(${r},${g},${b},${darken.toFixed(4)})`);
24
+ const red = (color >>> 24) & 0xff;
25
+ const green = (color >>> 16) & 0xff;
26
+ const blue = (color >>> 8) & 0xff;
27
+ // The Gl recipe measures distance in NORMALIZED uv — `length(uv - 0.5) * sqrt(2)`, which is 1.0 at the
28
+ // corner — so its iso-darkness contours are ellipses in pixel space whenever the frame is not square.
29
+ // Drawing the gradient in a unit-square space and letting the transform stretch it reproduces that
30
+ // geometry exactly; a circular gradient in pixel space would be a different shape on every non-square
31
+ // target. A unit-space radius of 1/sqrt(2) is the corner, so a Gl distance d maps to d/sqrt(2) here.
32
+ const toUnitRadius = 1 / Math.SQRT2;
33
+ const outerRadius = Math.max(0, Math.min(radius, 1)) * toUnitRadius;
34
+ // A zero-width ramp is a hard edge; createRadialGradient requires r0 < r1, so keep a hair of width.
35
+ const innerRadius = Math.max(0, Math.min(outerRadius - softness * toUnitRadius, outerRadius - 1e-4));
36
+ const gradient = ctx.createRadialGradient(0.5, 0.5, innerRadius, 0.5, 0.5, outerRadius);
37
+ // Gl eases with smoothstep, and a Canvas gradient interpolates its stops LINEARLY, so the curve has to
38
+ // be sampled into stops rather than declared. Substituting a straight line here would keep both
39
+ // endpoints correct and still put the midpoint ~9% of full darkening away from the Gl render.
40
+ for (let stop = 0; stop <= RAMP_STOPS; stop++) {
41
+ const t = stop / RAMP_STOPS;
42
+ const eased = t * t * (3 - 2 * t);
43
+ gradient.addColorStop(t, `rgba(${red},${green},${blue},${(darken * eased).toFixed(4)})`);
44
+ }
31
45
  ctx.save();
32
- ctx.setTransform(1, 0, 0, 1, 0, 0);
46
+ // Unit-square space: (0,0)-(1,1) covers the target, so the circular gradient above becomes the same
47
+ // ellipse Gl draws. Everything past `outerRadius` keeps the final stop's color, which is the clamp
48
+ // that makes "full dark at radius, and darker nowhere else" true out to the corners.
49
+ ctx.setTransform(w, 0, 0, h, 0, 0);
33
50
  ctx.globalCompositeOperation = 'multiply';
34
51
  ctx.filter = 'none';
35
52
  ctx.fillStyle = gradient;
36
- ctx.fillRect(0, 0, w, h);
53
+ ctx.fillRect(0, 0, 1, 1);
37
54
  ctx.restore();
38
55
  }
39
56
  export const defaultCanvasVignetteEffectRunner = (ctx, effect) => {
@@ -1 +1 @@
1
- {"version":3,"file":"canvasVignetteEffect.js","sourceRoot":"","sources":["../src/canvasVignetteEffect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"canvasVignetteEffect.js","sourceRoot":"","sources":["../src/canvasVignetteEffect.ts"],"names":[],"mappings":"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"}
@@ -12,9 +12,12 @@ export * from './canvasGradientBevelEffect';
12
12
  export * from './canvasGradientGlowEffect';
13
13
  export * from './canvasGradientRamp';
14
14
  export * from './canvasInnerGlowEffect';
15
+ export * from './canvasLensDistortionEffect';
16
+ export * from './canvasTiltShiftEffect';
15
17
  export * from './canvasInnerShadowEffect';
16
18
  export * from './canvasOuterGlowEffect';
17
19
  export * from './canvasPixelateEffect';
20
+ export * from './canvasPosterizeEffect';
18
21
  export * from './canvasRenderEffectPipeline';
19
22
  export * from './canvasRenderEffectRegistry';
20
23
  export * from './canvasRenderTextureEffect';
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
package/dist/contract.js CHANGED
@@ -12,9 +12,12 @@ export * from './canvasGradientBevelEffect';
12
12
  export * from './canvasGradientGlowEffect';
13
13
  export * from './canvasGradientRamp';
14
14
  export * from './canvasInnerGlowEffect';
15
+ export * from './canvasLensDistortionEffect';
16
+ export * from './canvasTiltShiftEffect';
15
17
  export * from './canvasInnerShadowEffect';
16
18
  export * from './canvasOuterGlowEffect';
17
19
  export * from './canvasPixelateEffect';
20
+ export * from './canvasPosterizeEffect';
18
21
  export * from './canvasRenderEffectPipeline';
19
22
  export * from './canvasRenderEffectRegistry';
20
23
  export * from './canvasRenderTextureEffect';
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { applyCanvasGradientRampLookup, applyCanvasRenderEffectsToRenderTexture, beginCanvasRenderEffectPipeline, buildCanvasGradientRamp, clipCanvasBevelBand, createCanvasRenderEffectPipeline, defaultCanvasBevelEffectRunner, defaultCanvasBlendEffectRunner, defaultCanvasBloomEffectRunner, defaultCanvasBlurEffectRunner, defaultCanvasCompositeEffectRunner, defaultCanvasDropShadowEffectRunner, defaultCanvasFilmGrainEffectRunner, defaultCanvasGradientBevelEffectRunner, defaultCanvasGradientGlowEffectRunner, defaultCanvasInnerGlowEffectRunner, defaultCanvasInnerShadowEffectRunner, defaultCanvasOuterGlowEffectRunner, defaultCanvasPixelateEffectRunner, defaultCanvasScanlinesEffectRunner, defaultCanvasVignetteEffectRunner, endCanvasRenderEffectPipeline, getCanvasBlendEffectBackdrop, getCanvasBlendEffectCompositeOperation, getCanvasCompositeEffectOperation, registerCanvasBevelEffect, registerCanvasBlendEffect, registerCanvasBlendEffectBackdrop, registerCanvasBloomEffect, registerCanvasBlurEffect, registerCanvasCompositeEffect, registerCanvasDropShadowEffect, registerCanvasFilmGrainEffect, registerCanvasGradientBevelEffect, registerCanvasGradientGlowEffect, registerCanvasInnerGlowEffect, registerCanvasInnerShadowEffect, registerCanvasOuterGlowEffect, registerCanvasPixelateEffect, registerCanvasRenderEffect, registerCanvasScanlinesEffect, registerCanvasVignetteEffect, unregisterCanvasBlendEffectBackdrop, } from './contract';
1
+ export { applyCanvasGradientRampLookup, applyCanvasRenderEffectsToRenderTexture, beginCanvasRenderEffectPipeline, buildCanvasGradientRamp, clipCanvasBevelBand, createCanvasRenderEffectPipeline, defaultCanvasBevelEffectRunner, defaultCanvasBlendEffectRunner, defaultCanvasBloomEffectRunner, defaultCanvasBlurEffectRunner, defaultCanvasCompositeEffectRunner, defaultCanvasDropShadowEffectRunner, defaultCanvasFilmGrainEffectRunner, defaultCanvasGradientBevelEffectRunner, defaultCanvasGradientGlowEffectRunner, defaultCanvasInnerGlowEffectRunner, defaultCanvasInnerShadowEffectRunner, defaultCanvasOuterGlowEffectRunner, defaultCanvasPixelateEffectRunner, defaultCanvasLensDistortionEffectRunner, defaultCanvasTiltShiftEffectRunner, defaultCanvasPosterizeEffectRunner, defaultCanvasScanlinesEffectRunner, defaultCanvasVignetteEffectRunner, endCanvasRenderEffectPipeline, getCanvasBlendEffectBackdrop, getCanvasBlendEffectCompositeOperation, getCanvasCompositeEffectOperation, registerCanvasBevelEffect, registerCanvasBlendEffect, registerCanvasBlendEffectBackdrop, registerCanvasBloomEffect, registerCanvasBlurEffect, registerCanvasCompositeEffect, registerCanvasDropShadowEffect, registerCanvasFilmGrainEffect, registerCanvasGradientBevelEffect, registerCanvasGradientGlowEffect, registerCanvasInnerGlowEffect, registerCanvasInnerShadowEffect, registerCanvasOuterGlowEffect, registerCanvasPixelateEffect, registerCanvasLensDistortionEffect, registerCanvasTiltShiftEffect, registerCanvasPosterizeEffect, registerCanvasRenderEffect, registerCanvasScanlinesEffect, registerCanvasVignetteEffect, unregisterCanvasBlendEffectBackdrop, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uCAAuC,EACvC,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,gCAAgC,EAChC,8BAA8B,EAC9B,8BAA8B,EAC9B,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,kCAAkC,EAClC,sCAAsC,EACtC,qCAAqC,EACrC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAClC,iCAAiC,EACjC,kCAAkC,EAClC,iCAAiC,EACjC,6BAA6B,EAC7B,4BAA4B,EAC5B,sCAAsC,EACtC,iCAAiC,EACjC,yBAAyB,EACzB,yBAAyB,EACzB,iCAAiC,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,6BAA6B,EAC7B,iCAAiC,EACjC,gCAAgC,EAChC,6BAA6B,EAC7B,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uCAAuC,EACvC,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,gCAAgC,EAChC,8BAA8B,EAC9B,8BAA8B,EAC9B,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,kCAAkC,EAClC,sCAAsC,EACtC,qCAAqC,EACrC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAClC,iCAAiC,EACjC,uCAAuC,EACvC,kCAAkC,EAClC,kCAAkC,EAClC,kCAAkC,EAClC,iCAAiC,EACjC,6BAA6B,EAC7B,4BAA4B,EAC5B,sCAAsC,EACtC,iCAAiC,EACjC,yBAAyB,EACzB,yBAAyB,EACzB,iCAAiC,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,6BAA6B,EAC7B,iCAAiC,EACjC,gCAAgC,EAChC,6BAA6B,EAC7B,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,kCAAkC,EAClC,6BAA6B,EAC7B,6BAA6B,EAC7B,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { applyCanvasGradientRampLookup, applyCanvasRenderEffectsToRenderTexture, beginCanvasRenderEffectPipeline, buildCanvasGradientRamp, clipCanvasBevelBand, createCanvasRenderEffectPipeline, defaultCanvasBevelEffectRunner, defaultCanvasBlendEffectRunner, defaultCanvasBloomEffectRunner, defaultCanvasBlurEffectRunner, defaultCanvasCompositeEffectRunner, defaultCanvasDropShadowEffectRunner, defaultCanvasFilmGrainEffectRunner, defaultCanvasGradientBevelEffectRunner, defaultCanvasGradientGlowEffectRunner, defaultCanvasInnerGlowEffectRunner, defaultCanvasInnerShadowEffectRunner, defaultCanvasOuterGlowEffectRunner, defaultCanvasPixelateEffectRunner, defaultCanvasScanlinesEffectRunner, defaultCanvasVignetteEffectRunner, endCanvasRenderEffectPipeline, getCanvasBlendEffectBackdrop, getCanvasBlendEffectCompositeOperation, getCanvasCompositeEffectOperation, registerCanvasBevelEffect, registerCanvasBlendEffect, registerCanvasBlendEffectBackdrop, registerCanvasBloomEffect, registerCanvasBlurEffect, registerCanvasCompositeEffect, registerCanvasDropShadowEffect, registerCanvasFilmGrainEffect, registerCanvasGradientBevelEffect, registerCanvasGradientGlowEffect, registerCanvasInnerGlowEffect, registerCanvasInnerShadowEffect, registerCanvasOuterGlowEffect, registerCanvasPixelateEffect, registerCanvasRenderEffect, registerCanvasScanlinesEffect, registerCanvasVignetteEffect, unregisterCanvasBlendEffectBackdrop, } from './contract';
1
+ export { applyCanvasGradientRampLookup, applyCanvasRenderEffectsToRenderTexture, beginCanvasRenderEffectPipeline, buildCanvasGradientRamp, clipCanvasBevelBand, createCanvasRenderEffectPipeline, defaultCanvasBevelEffectRunner, defaultCanvasBlendEffectRunner, defaultCanvasBloomEffectRunner, defaultCanvasBlurEffectRunner, defaultCanvasCompositeEffectRunner, defaultCanvasDropShadowEffectRunner, defaultCanvasFilmGrainEffectRunner, defaultCanvasGradientBevelEffectRunner, defaultCanvasGradientGlowEffectRunner, defaultCanvasInnerGlowEffectRunner, defaultCanvasInnerShadowEffectRunner, defaultCanvasOuterGlowEffectRunner, defaultCanvasPixelateEffectRunner, defaultCanvasLensDistortionEffectRunner, defaultCanvasTiltShiftEffectRunner, defaultCanvasPosterizeEffectRunner, defaultCanvasScanlinesEffectRunner, defaultCanvasVignetteEffectRunner, endCanvasRenderEffectPipeline, getCanvasBlendEffectBackdrop, getCanvasBlendEffectCompositeOperation, getCanvasCompositeEffectOperation, registerCanvasBevelEffect, registerCanvasBlendEffect, registerCanvasBlendEffectBackdrop, registerCanvasBloomEffect, registerCanvasBlurEffect, registerCanvasCompositeEffect, registerCanvasDropShadowEffect, registerCanvasFilmGrainEffect, registerCanvasGradientBevelEffect, registerCanvasGradientGlowEffect, registerCanvasInnerGlowEffect, registerCanvasInnerShadowEffect, registerCanvasOuterGlowEffect, registerCanvasPixelateEffect, registerCanvasLensDistortionEffect, registerCanvasTiltShiftEffect, registerCanvasPosterizeEffect, registerCanvasRenderEffect, registerCanvasScanlinesEffect, registerCanvasVignetteEffect, unregisterCanvasBlendEffectBackdrop, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uCAAuC,EACvC,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,gCAAgC,EAChC,8BAA8B,EAC9B,8BAA8B,EAC9B,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,kCAAkC,EAClC,sCAAsC,EACtC,qCAAqC,EACrC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAClC,iCAAiC,EACjC,kCAAkC,EAClC,iCAAiC,EACjC,6BAA6B,EAC7B,4BAA4B,EAC5B,sCAAsC,EACtC,iCAAiC,EACjC,yBAAyB,EACzB,yBAAyB,EACzB,iCAAiC,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,6BAA6B,EAC7B,iCAAiC,EACjC,gCAAgC,EAChC,6BAA6B,EAC7B,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uCAAuC,EACvC,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,gCAAgC,EAChC,8BAA8B,EAC9B,8BAA8B,EAC9B,8BAA8B,EAC9B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,kCAAkC,EAClC,sCAAsC,EACtC,qCAAqC,EACrC,kCAAkC,EAClC,oCAAoC,EACpC,kCAAkC,EAClC,iCAAiC,EACjC,uCAAuC,EACvC,kCAAkC,EAClC,kCAAkC,EAClC,kCAAkC,EAClC,iCAAiC,EACjC,6BAA6B,EAC7B,4BAA4B,EAC5B,sCAAsC,EACtC,iCAAiC,EACjC,yBAAyB,EACzB,yBAAyB,EACzB,iCAAiC,EACjC,yBAAyB,EACzB,wBAAwB,EACxB,6BAA6B,EAC7B,8BAA8B,EAC9B,6BAA6B,EAC7B,iCAAiC,EACjC,gCAAgC,EAChC,6BAA6B,EAC7B,+BAA+B,EAC/B,6BAA6B,EAC7B,4BAA4B,EAC5B,kCAAkC,EAClC,6BAA6B,EAC7B,6BAA6B,EAC7B,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/effects-canvas",
3
- "version": "0.4.0-next.1416.ef4fae7",
3
+ "version": "0.4.0-next.1487.e25e95d",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,13 +38,13 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/color": "0.4.0-next.1416.ef4fae7",
42
- "@flighthq/adjustments": "0.4.0-next.1416.ef4fae7",
43
- "@flighthq/scene2d-canvas": "0.4.0-next.1416.ef4fae7",
44
- "@flighthq/effects": "0.4.0-next.1416.ef4fae7",
45
- "@flighthq/geometry": "0.4.0-next.1416.ef4fae7",
46
- "@flighthq/registry": "0.4.0-next.1416.ef4fae7",
47
- "@flighthq/types": "0.4.0-next.1416.ef4fae7"
41
+ "@flighthq/color": "0.4.0-next.1487.e25e95d",
42
+ "@flighthq/adjustments": "0.4.0-next.1487.e25e95d",
43
+ "@flighthq/scene2d-canvas": "0.4.0-next.1487.e25e95d",
44
+ "@flighthq/effects": "0.4.0-next.1487.e25e95d",
45
+ "@flighthq/geometry": "0.4.0-next.1487.e25e95d",
46
+ "@flighthq/registry": "0.4.0-next.1487.e25e95d",
47
+ "@flighthq/types": "0.4.0-next.1487.e25e95d"
48
48
  },
49
49
  "devDependencies": {
50
50
  "typescript": "^5.3.0"
@@ -0,0 +1,149 @@
1
+ import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
2
+ import type { CanvasRenderTarget, LensDistortionEffect } from '@flighthq/types/contract';
3
+
4
+ import {
5
+ applyLensDistortionEffectToCanvas,
6
+ defaultCanvasLensDistortionEffectRunner,
7
+ registerCanvasLensDistortionEffect,
8
+ } from './canvasLensDistortionEffect';
9
+ import { getCanvasRenderEffectRunner } from './canvasRenderEffectRegistry';
10
+
11
+ // A width x height RGBA buffer whose red channel encodes the column and green the row, so a resample can
12
+ // be read back as "which source pixel did this destination pixel come from".
13
+ function createTargets(
14
+ width: number,
15
+ height: number,
16
+ ): { dest: CanvasRenderTarget; source: CanvasRenderTarget; written: { data: Uint8ClampedArray | null } } {
17
+ const pixels = new Uint8ClampedArray(width * height * 4);
18
+ for (let y = 0; y < height; y++) {
19
+ for (let x = 0; x < width; x++) {
20
+ const at = (y * width + x) * 4;
21
+ pixels[at] = Math.round((x / Math.max(1, width - 1)) * 255);
22
+ pixels[at + 1] = Math.round((y / Math.max(1, height - 1)) * 255);
23
+ pixels[at + 2] = 128;
24
+ pixels[at + 3] = 255;
25
+ }
26
+ }
27
+ const imageData = { data: pixels };
28
+ const written: { data: Uint8ClampedArray | null } = { data: null };
29
+ const source = {
30
+ context: { getImageData: () => imageData },
31
+ height,
32
+ width,
33
+ } as unknown as CanvasRenderTarget;
34
+ const dest = {
35
+ context: {
36
+ clearRect: () => {},
37
+ filter: 'none',
38
+ globalAlpha: 1,
39
+ globalCompositeOperation: 'source-over',
40
+ putImageData: (value: { data: Uint8ClampedArray }) => {
41
+ written.data = value.data;
42
+ },
43
+ restore: () => {},
44
+ save: () => {},
45
+ setTransform: () => {},
46
+ },
47
+ height,
48
+ width,
49
+ } as unknown as CanvasRenderTarget;
50
+ return { dest, source, written };
51
+ }
52
+
53
+ const at = (data: Uint8ClampedArray, width: number, x: number, y: number): number[] => {
54
+ const i = (y * width + x) * 4;
55
+ return [data[i]!, data[i + 1]!, data[i + 2]!, data[i + 3]!];
56
+ };
57
+
58
+ describe('applyLensDistortionEffectToCanvas', () => {
59
+ // amount 0 makes the polynomial the identity, so any deviation here is the resampler's own error
60
+ // rather than the distortion's — the one case where the output is knowable exactly.
61
+ it('is the identity at amount 0', () => {
62
+ const { dest, source, written } = createTargets(8, 8);
63
+
64
+ applyLensDistortionEffectToCanvas(source, dest, { amount: 0, kind: 'LensDistortionEffect' });
65
+
66
+ const original = source.context.getImageData(0, 0, 8, 8).data;
67
+ expect([...written.data!]).toEqual([...original]);
68
+ });
69
+
70
+ // ★ THE CENTRE IS THE FIXED POINT OF THE POLYNOMIAL, so it must not move for any amount. A resampler
71
+ // that is half a pixel off everywhere still passes an "output changed" check and fails this one.
72
+ it('leaves the centre pixel where it was', () => {
73
+ const { dest, source, written } = createTargets(9, 9);
74
+ const before = at(source.context.getImageData(0, 0, 9, 9).data, 9, 4, 4);
75
+
76
+ applyLensDistortionEffectToCanvas(source, dest, { amount: 0.4, kind: 'LensDistortionEffect' });
77
+
78
+ expect(at(written.data!, 9, 4, 4)).toEqual(before);
79
+ });
80
+
81
+ // Positive amount is barrel: a destination pixel away from the centre samples FURTHER out, so the
82
+ // gradient it reads is from nearer the edge — the red channel at a right-of-centre pixel rises.
83
+ it('samples further from the centre for a positive amount', () => {
84
+ const { dest, source, written } = createTargets(16, 16);
85
+ const before = at(source.context.getImageData(0, 0, 16, 16).data, 16, 12, 8)[0]!;
86
+
87
+ applyLensDistortionEffectToCanvas(source, dest, { amount: 0.5, kind: 'LensDistortionEffect' });
88
+
89
+ expect(at(written.data!, 16, 12, 8)[0]!).toBeGreaterThan(before);
90
+ });
91
+
92
+ // Negative amount is pincushion — the opposite sign, which a magnitude-only test would miss.
93
+ it('samples closer to the centre for a negative amount', () => {
94
+ const { dest, source, written } = createTargets(16, 16);
95
+ const before = at(source.context.getImageData(0, 0, 16, 16).data, 16, 12, 8)[0]!;
96
+
97
+ applyLensDistortionEffectToCanvas(source, dest, { amount: -0.5, kind: 'LensDistortionEffect' });
98
+
99
+ expect(at(written.data!, 16, 12, 8)[0]!).toBeLessThan(before);
100
+ });
101
+
102
+ // Where the remap leaves the frame the shader writes opaque black rather than clamping, so the canvas
103
+ // pass must too: clamping would silently smear the edge row outward instead of showing the boundary.
104
+ it('writes opaque black where the sample leaves the frame', () => {
105
+ const { dest, source, written } = createTargets(16, 16);
106
+
107
+ applyLensDistortionEffectToCanvas(source, dest, { amount: 3, kind: 'LensDistortionEffect' });
108
+
109
+ expect(at(written.data!, 16, 0, 0)).toEqual([0, 0, 0, 255]);
110
+ });
111
+
112
+ it('re-frames with scale so a larger scale pulls the sample inward', () => {
113
+ const { dest, source, written } = createTargets(16, 16);
114
+ const wide = createTargets(16, 16);
115
+
116
+ applyLensDistortionEffectToCanvas(source, dest, { amount: 0.5, kind: 'LensDistortionEffect' });
117
+ applyLensDistortionEffectToCanvas(wide.source, wide.dest, {
118
+ amount: 0.5,
119
+ kind: 'LensDistortionEffect',
120
+ scale: 1.5,
121
+ });
122
+
123
+ expect(at(wide.written.data!, 16, 14, 8)[0]!).toBeLessThan(at(written.data!, 16, 14, 8)[0]!);
124
+ });
125
+ });
126
+
127
+ describe('defaultCanvasLensDistortionEffectRunner', () => {
128
+ it('routes the runner context through to the pass', () => {
129
+ const { dest, pool, source, written } = { ...createTargets(8, 8), pool: { free: [], inUse: [] } };
130
+
131
+ defaultCanvasLensDistortionEffectRunner({ dest, pool, source, state: {} as never }, {
132
+ amount: 0,
133
+ kind: 'LensDistortionEffect',
134
+ } as LensDistortionEffect);
135
+
136
+ expect(written.data).not.toBeNull();
137
+ });
138
+ });
139
+
140
+ describe('registerCanvasLensDistortionEffect', () => {
141
+ it('makes the runner resolvable for the LensDistortionEffect kind', () => {
142
+ const context = { canvas: {}, getContextAttributes: () => ({}) };
143
+ const state = createCanvasRenderState({ getContext: () => context } as unknown as HTMLCanvasElement, {});
144
+
145
+ expect(getCanvasRenderEffectRunner(state, 'LensDistortionEffect')).toBeNull();
146
+ registerCanvasLensDistortionEffect(state);
147
+ expect(getCanvasRenderEffectRunner(state, 'LensDistortionEffect')).toBe(defaultCanvasLensDistortionEffectRunner);
148
+ });
149
+ });
@@ -0,0 +1,138 @@
1
+ import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
2
+ import type { CanvasRenderTarget, PosterizeEffect } from '@flighthq/types/contract';
3
+
4
+ import {
5
+ applyPosterizeEffectToCanvas,
6
+ defaultCanvasPosterizeEffectRunner,
7
+ registerCanvasPosterizeEffect,
8
+ } from './canvasPosterizeEffect';
9
+ import { getCanvasRenderEffectRunner } from './canvasRenderEffectRegistry';
10
+
11
+ // Stand-ins for the two contexts drawCanvasImageDataPass touches, matching the shape
12
+ // canvasColorMatrixPass.test.ts uses for the same reason: it keeps the assertion independent of module
13
+ // load order in a non-isolated suite.
14
+ function createStubTargets(pixels: ReadonlyArray<number>): {
15
+ dest: CanvasRenderTarget;
16
+ source: CanvasRenderTarget;
17
+ written: { data: Uint8ClampedArray | null };
18
+ } {
19
+ const imageData = { data: new Uint8ClampedArray(pixels) };
20
+ const written: { data: Uint8ClampedArray | null } = { data: null };
21
+ const source = {
22
+ context: { getImageData: () => imageData },
23
+ height: 1,
24
+ width: pixels.length / 4,
25
+ } as unknown as CanvasRenderTarget;
26
+ const dest = {
27
+ context: {
28
+ clearRect: () => {},
29
+ filter: 'none',
30
+ globalAlpha: 1,
31
+ globalCompositeOperation: 'source-over',
32
+ putImageData: (value: { data: Uint8ClampedArray }) => {
33
+ written.data = value.data;
34
+ },
35
+ restore: () => {},
36
+ save: () => {},
37
+ setTransform: () => {},
38
+ },
39
+ height: 1,
40
+ width: pixels.length / 4,
41
+ } as unknown as CanvasRenderTarget;
42
+ return { dest, source, written };
43
+ }
44
+
45
+ // The Gl/Wgpu recipe, in the float domain: floor(c * levels) / (levels - 1), clamped. Reimplemented here
46
+ // from the shader rather than from the implementation under test, so the two can disagree.
47
+ function expected(byte: number, levels: number): number {
48
+ const value = byte / 255;
49
+ const quantised = Math.floor(value * levels) / (levels - 1);
50
+ return Math.round(Math.max(0, Math.min(1, quantised)) * 255);
51
+ }
52
+
53
+ describe('applyPosterizeEffectToCanvas', () => {
54
+ it('quantises each channel to the levels the shader would produce', () => {
55
+ const input = [0, 64, 128, 255, 192, 200, 255, 255];
56
+ const { dest, source, written } = createStubTargets(input);
57
+
58
+ applyPosterizeEffectToCanvas(source, dest, { kind: 'PosterizeEffect', levels: 4 });
59
+
60
+ expect(written.data).not.toBeNull();
61
+ expect([...written.data!]).toEqual([
62
+ expected(0, 4),
63
+ expected(64, 4),
64
+ expected(128, 4),
65
+ 255,
66
+ expected(192, 4),
67
+ expected(200, 4),
68
+ expected(255, 4),
69
+ 255,
70
+ ]);
71
+ });
72
+
73
+ // ★ THE STEP FUNCTION IS THE POINT, so the test has to show a step. Two inputs inside one level must
74
+ // come out identical and two inputs across a boundary must not — a pass that merely dimmed every
75
+ // channel would satisfy a "value changed" assertion and fail this one.
76
+ it('collapses values inside a level and separates values across one', () => {
77
+ const { dest, source, written } = createStubTargets([100, 120, 200, 255]);
78
+
79
+ applyPosterizeEffectToCanvas(source, dest, { kind: 'PosterizeEffect', levels: 4 });
80
+
81
+ const [red, green, blue] = written.data!;
82
+ expect(red).toBe(green); // 100 and 120 both sit in the second quarter
83
+ expect(blue).not.toBe(red); // 200 is in the fourth
84
+ });
85
+
86
+ it('leaves alpha untouched', () => {
87
+ const { dest, source, written } = createStubTargets([10, 20, 30, 40]);
88
+
89
+ applyPosterizeEffectToCanvas(source, dest, { kind: 'PosterizeEffect', levels: 8 });
90
+
91
+ expect(written.data![3]).toBe(40);
92
+ });
93
+
94
+ // levels below 2 would divide by zero in (levels - 1); the Gl runner clamps to 2 and so must this one.
95
+ it('clamps levels to at least 2 rather than dividing by zero', () => {
96
+ const { dest, source, written } = createStubTargets([0, 128, 255, 255]);
97
+
98
+ applyPosterizeEffectToCanvas(source, dest, { kind: 'PosterizeEffect', levels: 1 });
99
+
100
+ expect([...written.data!].every((value) => Number.isFinite(value))).toBe(true);
101
+ expect([...written.data!].slice(0, 3)).toEqual([expected(0, 2), expected(128, 2), expected(255, 2)]);
102
+ });
103
+
104
+ it('defaults to 8 levels when none is given', () => {
105
+ const { dest, source, written } = createStubTargets([137, 137, 137, 255]);
106
+
107
+ applyPosterizeEffectToCanvas(source, dest, { kind: 'PosterizeEffect' });
108
+
109
+ expect(written.data![0]).toBe(expected(137, 8));
110
+ });
111
+ });
112
+
113
+ describe('defaultCanvasPosterizeEffectRunner', () => {
114
+ it('routes the runner context through to the pass', () => {
115
+ const { dest, source, written } = createStubTargets([255, 0, 0, 255]);
116
+
117
+ defaultCanvasPosterizeEffectRunner({ dest, pool: { free: [], inUse: [] }, source, state: {} as never }, {
118
+ kind: 'PosterizeEffect',
119
+ levels: 2,
120
+ } as PosterizeEffect);
121
+
122
+ expect(written.data![0]).toBe(expected(255, 2));
123
+ });
124
+ });
125
+
126
+ describe('registerCanvasPosterizeEffect', () => {
127
+ // Goes through the real registry rather than a stub table: registerCanvasRenderEffect writes into the
128
+ // state's RUNTIME registries, so a hand-made { renderEffects } object would assert against a shape the
129
+ // production path never touches — a test that passes while the registration goes somewhere else.
130
+ it('makes the runner resolvable for the PosterizeEffect kind', () => {
131
+ const context = { canvas: {}, getContextAttributes: () => ({}) };
132
+ const state = createCanvasRenderState({ getContext: () => context } as unknown as HTMLCanvasElement, {});
133
+
134
+ expect(getCanvasRenderEffectRunner(state, 'PosterizeEffect')).toBeNull();
135
+ registerCanvasPosterizeEffect(state);
136
+ expect(getCanvasRenderEffectRunner(state, 'PosterizeEffect')).toBe(defaultCanvasPosterizeEffectRunner);
137
+ });
138
+ });
@@ -0,0 +1,156 @@
1
+ import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
2
+ import type { CanvasRenderTarget, TiltShiftEffect } from '@flighthq/types/contract';
3
+
4
+ import { getCanvasRenderEffectRunner } from './canvasRenderEffectRegistry';
5
+ import {
6
+ applyTiltShiftEffectToCanvas,
7
+ defaultCanvasTiltShiftEffectRunner,
8
+ registerCanvasTiltShiftEffect,
9
+ } from './canvasTiltShiftEffect';
10
+
11
+ // A black field with a single white ROW at each named index — an impulse, whose response is the blur
12
+ // kernel itself. It is deliberately NOT a stripe pattern: the runner takes seven taps at an integer
13
+ // spacing, so a period-2 stripe field aliases into looking untouched no matter how large the radius
14
+ // is, on this backend and equally on the Gl one. An impulse has no period to resonate with.
15
+ function createImpulseTargets(
16
+ width: number,
17
+ height: number,
18
+ rows: ReadonlyArray<number>,
19
+ ): { dest: CanvasRenderTarget; source: CanvasRenderTarget; written: { data: Uint8ClampedArray | null } } {
20
+ const pixels = new Uint8ClampedArray(width * height * 4);
21
+ for (let y = 0; y < height; y++) {
22
+ const value = rows.includes(y) ? 255 : 0;
23
+ for (let x = 0; x < width; x++) {
24
+ const at = (y * width + x) * 4;
25
+ pixels[at] = value;
26
+ pixels[at + 1] = value;
27
+ pixels[at + 2] = value;
28
+ pixels[at + 3] = 255;
29
+ }
30
+ }
31
+ const imageData = { data: pixels };
32
+ const written: { data: Uint8ClampedArray | null } = { data: null };
33
+ const source = {
34
+ context: { getImageData: () => imageData },
35
+ height,
36
+ width,
37
+ } as unknown as CanvasRenderTarget;
38
+ const dest = {
39
+ context: {
40
+ clearRect: () => {},
41
+ filter: 'none',
42
+ globalAlpha: 1,
43
+ globalCompositeOperation: 'source-over',
44
+ putImageData: (value: { data: Uint8ClampedArray }) => {
45
+ written.data = value.data;
46
+ },
47
+ restore: () => {},
48
+ save: () => {},
49
+ setTransform: () => {},
50
+ },
51
+ height,
52
+ width,
53
+ } as unknown as CanvasRenderTarget;
54
+ return { dest, source, written };
55
+ }
56
+
57
+ const rowValue = (data: Uint8ClampedArray, width: number, y: number): number => data[y * width * 4]!;
58
+
59
+ describe('applyTiltShiftEffectToCanvas', () => {
60
+ // ★ THE BAND IS THE WHOLE EFFECT, so the test compares INSIDE it against OUTSIDE it in one image.
61
+ // An implementation that blurred the entire frame uniformly — the easiest thing to write by
62
+ // accident — passes any "the image got blurrier" assertion and fails this one.
63
+ it('keeps the focus band sharp while blurring above and below it', () => {
64
+ const { dest, source, written } = createImpulseTargets(2, 64, [6, 32]);
65
+
66
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 6, center: 0.5, kind: 'TiltShiftEffect', width: 0.2 });
67
+
68
+ const data = written.data!;
69
+ expect(rowValue(data, 2, 32)).toBe(255);
70
+ expect(rowValue(data, 2, 6)).toBeLessThan(80);
71
+ });
72
+
73
+ // The band follows `center`, and `center` is measured DOWN from the top edge. With the band near the
74
+ // top, the surviving impulse must be the top one — a runner that inherited the Gl flip keeps the
75
+ // bottom one instead, and a centred band cannot tell the two apart because |y - 0.5| is symmetric.
76
+ it('measures center down from the top edge', () => {
77
+ const { dest, source, written } = createImpulseTargets(2, 64, [6, 57]);
78
+
79
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 6, center: 0.1, kind: 'TiltShiftEffect', width: 0.15 });
80
+
81
+ const data = written.data!;
82
+ expect(rowValue(data, 2, 6)).toBe(255);
83
+ expect(rowValue(data, 2, 57)).toBeLessThan(80);
84
+ });
85
+
86
+ it('is the identity when blur is 0', () => {
87
+ const { dest, source, written } = createImpulseTargets(2, 16, [4]);
88
+ const original = [...source.context.getImageData(0, 0, 2, 16).data];
89
+
90
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 0, center: 0.5, kind: 'TiltShiftEffect', width: 0.2 });
91
+
92
+ expect([...written.data!]).toEqual(original);
93
+ });
94
+
95
+ // A band wide enough to cover the frame leaves everything in focus, so nothing may blur. This is the
96
+ // parameter's own extreme, and it separates `width` from `blur`.
97
+ it('leaves the whole frame sharp when the band covers it', () => {
98
+ const { dest, source, written } = createImpulseTargets(2, 32, [8, 24]);
99
+ const original = [...source.context.getImageData(0, 0, 2, 32).data];
100
+
101
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 8, center: 0.5, kind: 'TiltShiftEffect', width: 2 });
102
+
103
+ expect([...written.data!]).toEqual(original);
104
+ });
105
+
106
+ // ★ SEVEN EQUAL TAPS IS THE KERNEL THE OTHER BACKENDS DRAW, and an impulse measures it directly: a
107
+ // fully blurred white row keeps exactly 1/7 of its value, and each of the six offset rows receives
108
+ // the same 1/7. A Gaussian of the same nominal radius leaves far more at the centre and far less at
109
+ // the extremes, so this pins the weighting rather than merely observing that something spread.
110
+ it('spreads an impulse into seven equal taps', () => {
111
+ const { dest, source, written } = createImpulseTargets(2, 64, [32]);
112
+
113
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 6, center: 0.5, kind: 'TiltShiftEffect', width: 0.01 });
114
+
115
+ const data = written.data!;
116
+ const share = Math.round(255 / 7);
117
+ expect(rowValue(data, 2, 32)).toBe(share);
118
+ expect(rowValue(data, 2, 26)).toBe(share);
119
+ expect(rowValue(data, 2, 38)).toBe(share);
120
+ expect(rowValue(data, 2, 29)).toBe(0);
121
+ });
122
+
123
+ it('leaves alpha at full where the source is opaque', () => {
124
+ const { dest, source, written } = createImpulseTargets(2, 32, [16]);
125
+
126
+ applyTiltShiftEffectToCanvas(source, dest, { blur: 6, center: 0.5, kind: 'TiltShiftEffect', width: 0.2 });
127
+
128
+ expect(written.data![3]).toBe(255);
129
+ });
130
+ });
131
+
132
+ describe('defaultCanvasTiltShiftEffectRunner', () => {
133
+ it('routes the runner context through to the pass', () => {
134
+ const { dest, source, written } = createImpulseTargets(2, 16, [4]);
135
+
136
+ defaultCanvasTiltShiftEffectRunner({ dest, pool: { free: [], inUse: [] }, source, state: {} as never }, {
137
+ blur: 0,
138
+ kind: 'TiltShiftEffect',
139
+ } as TiltShiftEffect);
140
+
141
+ expect(written.data).not.toBeNull();
142
+ });
143
+ });
144
+
145
+ describe('registerCanvasTiltShiftEffect', () => {
146
+ // Through the real registry, not a stub table: registerCanvasRenderEffect writes into the state's
147
+ // runtime registries, so a hand-made object would assert against a shape production never touches.
148
+ it('makes the runner resolvable for the TiltShiftEffect kind', () => {
149
+ const context = { canvas: {}, getContextAttributes: () => ({}) };
150
+ const state = createCanvasRenderState({ getContext: () => context } as unknown as HTMLCanvasElement, {});
151
+
152
+ expect(getCanvasRenderEffectRunner(state, 'TiltShiftEffect')).toBeNull();
153
+ registerCanvasTiltShiftEffect(state);
154
+ expect(getCanvasRenderEffectRunner(state, 'TiltShiftEffect')).toBe(defaultCanvasTiltShiftEffectRunner);
155
+ });
156
+ });