@flighthq/effects 0.3.0-next.906.07cea63 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/bevelEffect.d.ts +3 -1
  2. package/dist/bevelEffect.d.ts.map +1 -1
  3. package/dist/bevelEffect.js +12 -0
  4. package/dist/bevelEffect.js.map +1 -1
  5. package/dist/blendModeMath.d.ts.map +1 -1
  6. package/dist/blendModeMath.js +4 -0
  7. package/dist/blendModeMath.js.map +1 -1
  8. package/dist/bloomEffect.d.ts +3 -1
  9. package/dist/bloomEffect.d.ts.map +1 -1
  10. package/dist/bloomEffect.js +11 -0
  11. package/dist/bloomEffect.js.map +1 -1
  12. package/dist/blurEffect.d.ts +3 -1
  13. package/dist/blurEffect.d.ts.map +1 -1
  14. package/dist/blurEffect.js +10 -0
  15. package/dist/blurEffect.js.map +1 -1
  16. package/dist/bokehDepthOfFieldEffect.d.ts +3 -1
  17. package/dist/bokehDepthOfFieldEffect.d.ts.map +1 -1
  18. package/dist/bokehDepthOfFieldEffect.js +11 -0
  19. package/dist/bokehDepthOfFieldEffect.js.map +1 -1
  20. package/dist/contactShadowsEffect.d.ts +3 -1
  21. package/dist/contactShadowsEffect.d.ts.map +1 -1
  22. package/dist/contactShadowsEffect.js +11 -0
  23. package/dist/contactShadowsEffect.js.map +1 -1
  24. package/dist/contract.d.ts +1 -0
  25. package/dist/contract.d.ts.map +1 -1
  26. package/dist/contract.js +1 -0
  27. package/dist/contract.js.map +1 -1
  28. package/dist/convolutionEffect.d.ts +3 -1
  29. package/dist/convolutionEffect.d.ts.map +1 -1
  30. package/dist/convolutionEffect.js +17 -0
  31. package/dist/convolutionEffect.js.map +1 -1
  32. package/dist/directionalBlurEffect.d.ts +3 -1
  33. package/dist/directionalBlurEffect.d.ts.map +1 -1
  34. package/dist/directionalBlurEffect.js +16 -0
  35. package/dist/directionalBlurEffect.js.map +1 -1
  36. package/dist/displacementEffect.d.ts +3 -1
  37. package/dist/displacementEffect.d.ts.map +1 -1
  38. package/dist/displacementEffect.js +13 -0
  39. package/dist/displacementEffect.js.map +1 -1
  40. package/dist/dropShadowEffect.d.ts +3 -1
  41. package/dist/dropShadowEffect.d.ts.map +1 -1
  42. package/dist/dropShadowEffect.js +12 -0
  43. package/dist/dropShadowEffect.js.map +1 -1
  44. package/dist/glitchEffect.d.ts +3 -1
  45. package/dist/glitchEffect.d.ts.map +1 -1
  46. package/dist/glitchEffect.js +13 -0
  47. package/dist/glitchEffect.js.map +1 -1
  48. package/dist/gradientBevelEffect.d.ts +3 -1
  49. package/dist/gradientBevelEffect.d.ts.map +1 -1
  50. package/dist/gradientBevelEffect.js +12 -0
  51. package/dist/gradientBevelEffect.js.map +1 -1
  52. package/dist/gradientGlowEffect.d.ts +3 -1
  53. package/dist/gradientGlowEffect.d.ts.map +1 -1
  54. package/dist/gradientGlowEffect.js +10 -0
  55. package/dist/gradientGlowEffect.js.map +1 -1
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/innerGlowEffect.d.ts +3 -1
  61. package/dist/innerGlowEffect.d.ts.map +1 -1
  62. package/dist/innerGlowEffect.js +10 -0
  63. package/dist/innerGlowEffect.js.map +1 -1
  64. package/dist/innerShadowEffect.d.ts +3 -1
  65. package/dist/innerShadowEffect.d.ts.map +1 -1
  66. package/dist/innerShadowEffect.js +12 -0
  67. package/dist/innerShadowEffect.js.map +1 -1
  68. package/dist/medianEffect.d.ts +3 -1
  69. package/dist/medianEffect.d.ts.map +1 -1
  70. package/dist/medianEffect.js +11 -0
  71. package/dist/medianEffect.js.map +1 -1
  72. package/dist/outerGlowEffect.d.ts +3 -1
  73. package/dist/outerGlowEffect.d.ts.map +1 -1
  74. package/dist/outerGlowEffect.js +10 -0
  75. package/dist/outerGlowEffect.js.map +1 -1
  76. package/dist/outlineEffect.d.ts +3 -1
  77. package/dist/outlineEffect.d.ts.map +1 -1
  78. package/dist/outlineEffect.js +11 -0
  79. package/dist/outlineEffect.js.map +1 -1
  80. package/dist/renderEffectPadding.d.ts +7 -0
  81. package/dist/renderEffectPadding.d.ts.map +1 -0
  82. package/dist/renderEffectPadding.js +70 -0
  83. package/dist/renderEffectPadding.js.map +1 -0
  84. package/dist/tiltShiftEffect.d.ts +3 -1
  85. package/dist/tiltShiftEffect.d.ts.map +1 -1
  86. package/dist/tiltShiftEffect.js +11 -0
  87. package/dist/tiltShiftEffect.js.map +1 -1
  88. package/package.json +6 -2
  89. package/src/bevelEffect.test.ts +24 -1
  90. package/src/blendModeMath.test.ts +15 -0
  91. package/src/bloomEffect.test.ts +31 -1
  92. package/src/blurEffect.test.ts +26 -1
  93. package/src/bokehDepthOfFieldEffect.test.ts +28 -1
  94. package/src/contactShadowsEffect.test.ts +28 -1
  95. package/src/convolutionEffect.test.ts +28 -1
  96. package/src/directionalBlurEffect.test.ts +28 -1
  97. package/src/displacementEffect.test.ts +28 -1
  98. package/src/dropShadowEffect.test.ts +28 -1
  99. package/src/glitchEffect.test.ts +24 -1
  100. package/src/gradientBevelEffect.test.ts +32 -1
  101. package/src/gradientGlowEffect.test.ts +27 -1
  102. package/src/innerGlowEffect.test.ts +28 -1
  103. package/src/innerShadowEffect.test.ts +30 -1
  104. package/src/medianEffect.test.ts +24 -1
  105. package/src/outerGlowEffect.test.ts +28 -1
  106. package/src/outlineEffect.test.ts +24 -1
  107. package/src/renderEffectPadding.test.ts +114 -0
  108. package/src/tiltShiftEffect.test.ts +28 -1
@@ -1,4 +1,10 @@
1
- import { createConvolutionEffect } from './convolutionEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createConvolutionEffect,
5
+ getConvolutionEffectPadding,
6
+ registerConvolutionEffectPaddingResolver,
7
+ } from './convolutionEffect';
2
8
 
3
9
  describe('createConvolutionEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -18,3 +24,24 @@ describe('createConvolutionEffect', () => {
18
24
  expect(effect).toMatchObject({ bias: 8, preserveAlpha: false });
19
25
  });
20
26
  });
27
+
28
+ describe('getConvolutionEffectPadding', () => {
29
+ it('preserves the exact asymmetric reach of even kernels', () => {
30
+ expect(getConvolutionEffectPadding(createConvolutionEffect({ matrix: [1, 1], matrixX: 2, matrixY: 1 }))).toEqual({
31
+ bottom: 0,
32
+ left: 0,
33
+ right: 1,
34
+ top: 0,
35
+ });
36
+ });
37
+ });
38
+
39
+ describe('registerConvolutionEffectPaddingResolver', () => {
40
+ it('registers the convolution footprint on only the supplied state', () => {
41
+ const state = createRenderState();
42
+ const other = createRenderState();
43
+ registerConvolutionEffectPaddingResolver(state);
44
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('ConvolutionEffect')).toBe(true);
45
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
46
+ });
47
+ });
@@ -1,4 +1,10 @@
1
- import { createDirectionalBlurEffect } from './directionalBlurEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createDirectionalBlurEffect,
5
+ getDirectionalBlurEffectPadding,
6
+ registerDirectionalBlurEffectPaddingResolver,
7
+ } from './directionalBlurEffect';
2
8
 
3
9
  describe('createDirectionalBlurEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -9,3 +15,24 @@ describe('createDirectionalBlurEffect', () => {
9
15
  expect(createDirectionalBlurEffect({ angle: 1, length: 8 })).toMatchObject({ angle: 1, length: 8 });
10
16
  });
11
17
  });
18
+
19
+ describe('getDirectionalBlurEffectPadding', () => {
20
+ it('projects the centered half-length onto both axes', () => {
21
+ expect(getDirectionalBlurEffectPadding(createDirectionalBlurEffect({ angle: Math.PI / 2, length: 9 }))).toEqual({
22
+ bottom: 5,
23
+ left: 0,
24
+ right: 0,
25
+ top: 5,
26
+ });
27
+ });
28
+ });
29
+
30
+ describe('registerDirectionalBlurEffectPaddingResolver', () => {
31
+ it('registers the directional-blur footprint on only the supplied state', () => {
32
+ const state = createRenderState();
33
+ const other = createRenderState();
34
+ registerDirectionalBlurEffectPaddingResolver(state);
35
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('DirectionalBlurEffect')).toBe(true);
36
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
37
+ });
38
+ });
@@ -1,4 +1,10 @@
1
- import { createDisplacementEffect } from './displacementEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createDisplacementEffect,
5
+ getDisplacementEffectPadding,
6
+ registerDisplacementEffectPaddingResolver,
7
+ } from './displacementEffect';
2
8
 
3
9
  describe('createDisplacementEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -13,3 +19,24 @@ describe('createDisplacementEffect', () => {
13
19
  });
14
20
  });
15
21
  });
22
+
23
+ describe('getDisplacementEffectPadding', () => {
24
+ it('covers the shader maximum two-axis sine warp', () => {
25
+ expect(getDisplacementEffectPadding(createDisplacementEffect({ intensity: 5 }))).toEqual({
26
+ bottom: 5,
27
+ left: 8,
28
+ right: 8,
29
+ top: 5,
30
+ });
31
+ });
32
+ });
33
+
34
+ describe('registerDisplacementEffectPaddingResolver', () => {
35
+ it('registers the displacement footprint on only the supplied state', () => {
36
+ const state = createRenderState();
37
+ const other = createRenderState();
38
+ registerDisplacementEffectPaddingResolver(state);
39
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('DisplacementEffect')).toBe(true);
40
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
41
+ });
42
+ });
@@ -1,4 +1,10 @@
1
- import { createDropShadowEffect } from './dropShadowEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createDropShadowEffect,
5
+ getDropShadowEffectPadding,
6
+ registerDropShadowEffectPaddingResolver,
7
+ } from './dropShadowEffect';
2
8
 
3
9
  describe('createDropShadowEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -12,3 +18,24 @@ describe('createDropShadowEffect', () => {
12
18
  });
13
19
  });
14
20
  });
21
+
22
+ describe('getDropShadowEffectPadding', () => {
23
+ it('adds the offset only to the reached side of the Gaussian footprint', () => {
24
+ expect(getDropShadowEffectPadding(createDropShadowEffect({ angle: 0, blurX: 2, blurY: 3, distance: 5 }))).toEqual({
25
+ bottom: 9,
26
+ left: 6,
27
+ right: 11,
28
+ top: 9,
29
+ });
30
+ });
31
+ });
32
+
33
+ describe('registerDropShadowEffectPaddingResolver', () => {
34
+ it('registers the drop-shadow footprint on only the supplied state', () => {
35
+ const state = createRenderState();
36
+ const other = createRenderState();
37
+ registerDropShadowEffectPaddingResolver(state);
38
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('DropShadowEffect')).toBe(true);
39
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
40
+ });
41
+ });
@@ -1,4 +1,6 @@
1
- import { createGlitchEffect } from './glitchEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import { createGlitchEffect, getGlitchEffectPadding, registerGlitchEffectPaddingResolver } from './glitchEffect';
2
4
 
3
5
  describe('createGlitchEffect', () => {
4
6
  it('tags the intent type', () => {
@@ -14,3 +16,24 @@ describe('createGlitchEffect', () => {
14
16
  });
15
17
  });
16
18
  });
19
+
20
+ describe('getGlitchEffectPadding', () => {
21
+ it('covers horizontal tears and the maximum RGB channel separation', () => {
22
+ expect(getGlitchEffectPadding(createGlitchEffect({ colorShift: 10, intensity: 0.5 }))).toEqual({
23
+ bottom: 0,
24
+ left: 34,
25
+ right: 34,
26
+ top: 0,
27
+ });
28
+ });
29
+ });
30
+
31
+ describe('registerGlitchEffectPaddingResolver', () => {
32
+ it('registers the glitch footprint on only the supplied state', () => {
33
+ const state = createRenderState();
34
+ const other = createRenderState();
35
+ registerGlitchEffectPaddingResolver(state);
36
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('GlitchEffect')).toBe(true);
37
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
38
+ });
39
+ });
@@ -1,4 +1,10 @@
1
- import { createGradientBevelEffect } from './gradientBevelEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createGradientBevelEffect,
5
+ getGradientBevelEffectPadding,
6
+ registerGradientBevelEffectPaddingResolver,
7
+ } from './gradientBevelEffect';
2
8
 
3
9
  describe('createGradientBevelEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -19,3 +25,28 @@ describe('createGradientBevelEffect', () => {
19
25
  ).toMatchObject({ sourceMode: 'hide', strength: 2 });
20
26
  });
21
27
  });
28
+
29
+ describe('getGradientBevelEffectPadding', () => {
30
+ it('adds the directional offset only to the reached sides of the Gaussian footprint', () => {
31
+ const effect = createGradientBevelEffect({
32
+ alphas: [1, 1],
33
+ angle: -90,
34
+ blurX: 2,
35
+ blurY: 3,
36
+ colors: [0xff0000, 0x00ff00],
37
+ distance: 5,
38
+ ratios: [0, 255],
39
+ });
40
+ expect(getGradientBevelEffectPadding(effect)).toEqual({ bottom: 9, left: 6, right: 6, top: 14 });
41
+ });
42
+ });
43
+
44
+ describe('registerGradientBevelEffectPaddingResolver', () => {
45
+ it('registers the gradient-bevel footprint on only the supplied state', () => {
46
+ const state = createRenderState();
47
+ const other = createRenderState();
48
+ registerGradientBevelEffectPaddingResolver(state);
49
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('GradientBevelEffect')).toBe(true);
50
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
51
+ });
52
+ });
@@ -1,4 +1,10 @@
1
- import { createGradientGlowEffect } from './gradientGlowEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createGradientGlowEffect,
5
+ getGradientGlowEffectPadding,
6
+ registerGradientGlowEffectPaddingResolver,
7
+ } from './gradientGlowEffect';
2
8
 
3
9
  describe('createGradientGlowEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -19,3 +25,23 @@ describe('createGradientGlowEffect', () => {
19
25
  ).toMatchObject({ sourceMode: 'knockout', strength: 2 });
20
26
  });
21
27
  });
28
+
29
+ describe('getGradientGlowEffectPadding', () => {
30
+ it('uses the three-sigma extent independently on both axes', () => {
31
+ expect(
32
+ getGradientGlowEffectPadding(
33
+ createGradientGlowEffect({ alphas: [1], blurX: 2.1, blurY: 3, colors: [0xffffff], ratios: [0] }),
34
+ ),
35
+ ).toEqual({ bottom: 9, left: 7, right: 7, top: 9 });
36
+ });
37
+ });
38
+
39
+ describe('registerGradientGlowEffectPaddingResolver', () => {
40
+ it('registers the gradient-glow footprint on only the supplied state', () => {
41
+ const state = createRenderState();
42
+ const other = createRenderState();
43
+ registerGradientGlowEffectPaddingResolver(state);
44
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('GradientGlowEffect')).toBe(true);
45
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
46
+ });
47
+ });
@@ -1,4 +1,10 @@
1
- import { createInnerGlowEffect } from './innerGlowEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createInnerGlowEffect,
5
+ getInnerGlowEffectPadding,
6
+ registerInnerGlowEffectPaddingResolver,
7
+ } from './innerGlowEffect';
2
8
 
3
9
  describe('createInnerGlowEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -13,3 +19,24 @@ describe('createInnerGlowEffect', () => {
13
19
  expect(createInnerGlowEffect({ sourceMode: 'hide' })).toMatchObject({ sourceMode: 'hide' });
14
20
  });
15
21
  });
22
+
23
+ describe('getInnerGlowEffectPadding', () => {
24
+ it('uses a three-sigma Gaussian footprint', () => {
25
+ expect(getInnerGlowEffectPadding(createInnerGlowEffect({ blurX: 2, blurY: 3 }))).toEqual({
26
+ bottom: 9,
27
+ left: 6,
28
+ right: 6,
29
+ top: 9,
30
+ });
31
+ });
32
+ });
33
+
34
+ describe('registerInnerGlowEffectPaddingResolver', () => {
35
+ it('registers the inner-glow footprint on only the supplied state', () => {
36
+ const state = createRenderState();
37
+ const other = createRenderState();
38
+ registerInnerGlowEffectPaddingResolver(state);
39
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('InnerGlowEffect')).toBe(true);
40
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
41
+ });
42
+ });
@@ -1,4 +1,10 @@
1
- import { createInnerShadowEffect } from './innerShadowEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createInnerShadowEffect,
5
+ getInnerShadowEffectPadding,
6
+ registerInnerShadowEffectPaddingResolver,
7
+ } from './innerShadowEffect';
2
8
 
3
9
  describe('createInnerShadowEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -13,3 +19,26 @@ describe('createInnerShadowEffect', () => {
13
19
  expect(createInnerShadowEffect({ sourceMode: 'hide' })).toMatchObject({ sourceMode: 'hide' });
14
20
  });
15
21
  });
22
+
23
+ describe('getInnerShadowEffectPadding', () => {
24
+ it('preserves the directional exterior samples needed by the clipped inner pass', () => {
25
+ expect(
26
+ getInnerShadowEffectPadding(createInnerShadowEffect({ angle: 90, blurX: 2, blurY: 3, distance: 5 })),
27
+ ).toEqual({
28
+ bottom: 14,
29
+ left: 6,
30
+ right: 6,
31
+ top: 9,
32
+ });
33
+ });
34
+ });
35
+
36
+ describe('registerInnerShadowEffectPaddingResolver', () => {
37
+ it('registers the inner-shadow footprint on only the supplied state', () => {
38
+ const state = createRenderState();
39
+ const other = createRenderState();
40
+ registerInnerShadowEffectPaddingResolver(state);
41
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('InnerShadowEffect')).toBe(true);
42
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
43
+ });
44
+ });
@@ -1,4 +1,6 @@
1
- import { createMedianEffect } from './medianEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import { createMedianEffect, getMedianEffectPadding, registerMedianEffectPaddingResolver } from './medianEffect';
2
4
 
3
5
  describe('createMedianEffect', () => {
4
6
  it('tags the intent type', () => {
@@ -9,3 +11,24 @@ describe('createMedianEffect', () => {
9
11
  expect(createMedianEffect({ radius: 2 })).toMatchObject({ radius: 2 });
10
12
  });
11
13
  });
14
+
15
+ describe('getMedianEffectPadding', () => {
16
+ it('uses the rounded neighborhood radius on every side', () => {
17
+ expect(getMedianEffectPadding(createMedianEffect({ radius: 1.6 }))).toEqual({
18
+ bottom: 2,
19
+ left: 2,
20
+ right: 2,
21
+ top: 2,
22
+ });
23
+ });
24
+ });
25
+
26
+ describe('registerMedianEffectPaddingResolver', () => {
27
+ it('registers the median footprint on only the supplied state', () => {
28
+ const state = createRenderState();
29
+ const other = createRenderState();
30
+ registerMedianEffectPaddingResolver(state);
31
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('MedianEffect')).toBe(true);
32
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
33
+ });
34
+ });
@@ -1,4 +1,10 @@
1
- import { createOuterGlowEffect } from './outerGlowEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createOuterGlowEffect,
5
+ getOuterGlowEffectPadding,
6
+ registerOuterGlowEffectPaddingResolver,
7
+ } from './outerGlowEffect';
2
8
 
3
9
  describe('createOuterGlowEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -12,3 +18,24 @@ describe('createOuterGlowEffect', () => {
12
18
  });
13
19
  });
14
20
  });
21
+
22
+ describe('getOuterGlowEffectPadding', () => {
23
+ it('uses a three-sigma Gaussian footprint', () => {
24
+ expect(getOuterGlowEffectPadding(createOuterGlowEffect({ blurX: 2, blurY: 3 }))).toEqual({
25
+ bottom: 9,
26
+ left: 6,
27
+ right: 6,
28
+ top: 9,
29
+ });
30
+ });
31
+ });
32
+
33
+ describe('registerOuterGlowEffectPaddingResolver', () => {
34
+ it('registers the outer-glow footprint on only the supplied state', () => {
35
+ const state = createRenderState();
36
+ const other = createRenderState();
37
+ registerOuterGlowEffectPaddingResolver(state);
38
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('OuterGlowEffect')).toBe(true);
39
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
40
+ });
41
+ });
@@ -1,4 +1,6 @@
1
- import { createOutlineEffect } from './outlineEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import { createOutlineEffect, getOutlineEffectPadding, registerOutlineEffectPaddingResolver } from './outlineEffect';
2
4
 
3
5
  describe('createOutlineEffect', () => {
4
6
  it('tags the intent type', () => {
@@ -13,3 +15,24 @@ describe('createOutlineEffect', () => {
13
15
  });
14
16
  });
15
17
  });
18
+
19
+ describe('getOutlineEffectPadding', () => {
20
+ it('uses the Sobel sampling thickness on every side', () => {
21
+ expect(getOutlineEffectPadding(createOutlineEffect({ thickness: 1.5 }))).toEqual({
22
+ bottom: 2,
23
+ left: 2,
24
+ right: 2,
25
+ top: 2,
26
+ });
27
+ });
28
+ });
29
+
30
+ describe('registerOutlineEffectPaddingResolver', () => {
31
+ it('registers the outline footprint on only the supplied state', () => {
32
+ const state = createRenderState();
33
+ const other = createRenderState();
34
+ registerOutlineEffectPaddingResolver(state);
35
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('OutlineEffect')).toBe(true);
36
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
37
+ });
38
+ });
@@ -0,0 +1,114 @@
1
+ import { createRenderState, enableRenderRegistrySignals, getRenderStateRuntime } from '@flighthq/render/contract';
2
+ import { connectSignal } from '@flighthq/signals/contract';
3
+ import type { RenderEffect, RenderState } from '@flighthq/types/contract';
4
+ import { RenderRegistry } from '@flighthq/types/contract';
5
+
6
+ import { createBlurEffect, registerBlurEffectPaddingResolver } from './blurEffect';
7
+ import {
8
+ computeRenderEffectPadding,
9
+ explainRenderEffectPadding,
10
+ getDirectionalRenderEffectPadding,
11
+ getGaussianRenderEffectPadding,
12
+ registerRenderEffectPaddingResolver,
13
+ } from './renderEffectPadding';
14
+
15
+ describe('computeRenderEffectPadding', () => {
16
+ let state: RenderState;
17
+
18
+ beforeEach(() => {
19
+ state = createRenderState();
20
+ registerBlurEffectPaddingResolver(state);
21
+ registerRenderEffectPaddingResolver(state, 'acme.Pointwise', () => ({
22
+ bottom: 0,
23
+ left: 0,
24
+ right: 0,
25
+ top: 0,
26
+ }));
27
+ });
28
+
29
+ it('adds each side across a sequential effect chain while pointwise effects add zero', () => {
30
+ expect(
31
+ computeRenderEffectPadding(state, [
32
+ createBlurEffect({ blurX: 2, blurY: 1 }),
33
+ { kind: 'acme.Pointwise' } as RenderEffect,
34
+ createBlurEffect({ blurX: 1, blurY: 4 }),
35
+ ]),
36
+ ).toEqual({ bottom: 15, left: 9, right: 9, top: 15 });
37
+ });
38
+
39
+ it('returns a zero sentinel for an unregistered kind', () => {
40
+ const effect = { kind: 'acme.Missing' } as RenderEffect;
41
+ expect(computeRenderEffectPadding(state, effect)).toEqual({ bottom: 0, left: 0, right: 0, top: 0 });
42
+ });
43
+
44
+ it('emits a shared registry miss only when the signal seam is enabled', () => {
45
+ const misses: Array<readonly [RenderRegistry, string]> = [];
46
+ connectSignal(enableRenderRegistrySignals(state).onRegistryMiss, (registry, kind) => {
47
+ misses.push([registry, kind]);
48
+ });
49
+ expect(computeRenderEffectPadding(state, { kind: 'acme.Missing' } as RenderEffect)).toEqual({
50
+ bottom: 0,
51
+ left: 0,
52
+ right: 0,
53
+ top: 0,
54
+ });
55
+ expect(misses).toEqual([[RenderRegistry.EffectPaddingResolver, 'acme.Missing']]);
56
+ });
57
+ });
58
+
59
+ describe('explainRenderEffectPadding', () => {
60
+ it('reports every missing resolver kind while preserving resolved padding', () => {
61
+ const state = createRenderState();
62
+ registerBlurEffectPaddingResolver(state);
63
+
64
+ expect(
65
+ explainRenderEffectPadding(state, [
66
+ createBlurEffect({ blurX: 2, blurY: 3 }),
67
+ { kind: 'acme.Missing' } as RenderEffect,
68
+ ]),
69
+ ).toEqual({
70
+ missingKinds: ['acme.Missing'],
71
+ padding: { bottom: 9, left: 6, right: 6, top: 9 },
72
+ status: 'missing-resolver',
73
+ });
74
+ });
75
+ });
76
+
77
+ describe('getDirectionalRenderEffectPadding', () => {
78
+ it('adds positive screen-space offsets only to the reached sides', () => {
79
+ expect(getDirectionalRenderEffectPadding(2, 3, 4.25, 5.5)).toEqual({
80
+ bottom: 15,
81
+ left: 6,
82
+ right: 11,
83
+ top: 9,
84
+ });
85
+ });
86
+
87
+ it('adds negative screen-space offsets only to left and top', () => {
88
+ expect(getDirectionalRenderEffectPadding(1, 1, -2.5, -4.25)).toEqual({
89
+ bottom: 3,
90
+ left: 6,
91
+ right: 3,
92
+ top: 8,
93
+ });
94
+ });
95
+ });
96
+
97
+ describe('getGaussianRenderEffectPadding', () => {
98
+ it('uses a sanitized three-sigma extent on each axis', () => {
99
+ expect(getGaussianRenderEffectPadding(2.1, -4)).toEqual({ bottom: 0, left: 7, right: 7, top: 0 });
100
+ });
101
+ });
102
+
103
+ describe('registerRenderEffectPaddingResolver', () => {
104
+ it('adds and removes a state-local resolver', () => {
105
+ const state = createRenderState();
106
+ const resolver = vi.fn(() => ({ bottom: 1, left: 2, right: 3, top: 4 }));
107
+
108
+ registerRenderEffectPaddingResolver(state, 'acme.Effect', resolver);
109
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.get('acme.Effect')).toBe(resolver);
110
+
111
+ registerRenderEffectPaddingResolver(state, 'acme.Effect', null);
112
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('acme.Effect')).toBe(false);
113
+ });
114
+ });
@@ -1,4 +1,10 @@
1
- import { createTiltShiftEffect } from './tiltShiftEffect';
1
+ import { createRenderState, getRenderStateRuntime } from '@flighthq/render/contract';
2
+
3
+ import {
4
+ createTiltShiftEffect,
5
+ getTiltShiftEffectPadding,
6
+ registerTiltShiftEffectPaddingResolver,
7
+ } from './tiltShiftEffect';
2
8
 
3
9
  describe('createTiltShiftEffect', () => {
4
10
  it('tags the intent type', () => {
@@ -13,3 +19,24 @@ describe('createTiltShiftEffect', () => {
13
19
  });
14
20
  });
15
21
  });
22
+
23
+ describe('getTiltShiftEffectPadding', () => {
24
+ it('covers the seven-tap vertical blur reach', () => {
25
+ expect(getTiltShiftEffectPadding(createTiltShiftEffect({ blur: 2.5 }))).toEqual({
26
+ bottom: 8,
27
+ left: 0,
28
+ right: 0,
29
+ top: 8,
30
+ });
31
+ });
32
+ });
33
+
34
+ describe('registerTiltShiftEffectPaddingResolver', () => {
35
+ it('registers the tilt-shift footprint on only the supplied state', () => {
36
+ const state = createRenderState();
37
+ const other = createRenderState();
38
+ registerTiltShiftEffectPaddingResolver(state);
39
+ expect(getRenderStateRuntime(state).renderEffectPaddingResolverRegistry?.has('TiltShiftEffect')).toBe(true);
40
+ expect(getRenderStateRuntime(other).renderEffectPaddingResolverRegistry).toBeNull();
41
+ });
42
+ });