@flighthq/effects-canvas 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.
- package/dist/canvasBevelEffect.d.ts +7 -0
- package/dist/canvasBevelEffect.d.ts.map +1 -0
- package/dist/canvasBevelEffect.js +76 -0
- package/dist/canvasBevelEffect.js.map +1 -0
- package/dist/canvasBlendEffect.d.ts +9 -0
- package/dist/canvasBlendEffect.d.ts.map +1 -0
- package/dist/canvasBlendEffect.js +89 -0
- package/dist/canvasBlendEffect.js.map +1 -0
- package/dist/canvasBloomEffect.d.ts +2 -1
- package/dist/canvasBloomEffect.d.ts.map +1 -1
- package/dist/canvasBloomEffect.js +4 -0
- package/dist/canvasBloomEffect.js.map +1 -1
- package/dist/canvasBlurEffect.d.ts +2 -1
- package/dist/canvasBlurEffect.d.ts.map +1 -1
- package/dist/canvasBlurEffect.js +4 -0
- package/dist/canvasBlurEffect.js.map +1 -1
- package/dist/canvasColorMatrixPass.d.ts +1 -0
- package/dist/canvasColorMatrixPass.d.ts.map +1 -1
- package/dist/canvasColorMatrixPass.js +22 -16
- package/dist/canvasColorMatrixPass.js.map +1 -1
- package/dist/canvasCompositeEffect.d.ts +6 -0
- package/dist/canvasCompositeEffect.d.ts.map +1 -0
- package/dist/canvasCompositeEffect.js +71 -0
- package/dist/canvasCompositeEffect.js.map +1 -0
- package/dist/canvasDropShadowEffect.d.ts +2 -1
- package/dist/canvasDropShadowEffect.d.ts.map +1 -1
- package/dist/canvasDropShadowEffect.js +4 -0
- package/dist/canvasDropShadowEffect.js.map +1 -1
- package/dist/canvasFilmGrainEffect.d.ts +2 -1
- package/dist/canvasFilmGrainEffect.d.ts.map +1 -1
- package/dist/canvasFilmGrainEffect.js +4 -0
- package/dist/canvasFilmGrainEffect.js.map +1 -1
- package/dist/canvasGradientBevelEffect.d.ts +6 -0
- package/dist/canvasGradientBevelEffect.d.ts.map +1 -0
- package/dist/canvasGradientBevelEffect.js +62 -0
- package/dist/canvasGradientBevelEffect.js.map +1 -0
- package/dist/canvasGradientGlowEffect.d.ts +6 -0
- package/dist/canvasGradientGlowEffect.d.ts.map +1 -0
- package/dist/canvasGradientGlowEffect.js +36 -0
- package/dist/canvasGradientGlowEffect.js.map +1 -0
- package/dist/canvasGradientRamp.d.ts +4 -0
- package/dist/canvasGradientRamp.d.ts.map +1 -0
- package/dist/canvasGradientRamp.js +75 -0
- package/dist/canvasGradientRamp.js.map +1 -0
- package/dist/canvasInnerGlowEffect.d.ts +6 -0
- package/dist/canvasInnerGlowEffect.d.ts.map +1 -0
- package/dist/canvasInnerGlowEffect.js +37 -0
- package/dist/canvasInnerGlowEffect.js.map +1 -0
- package/dist/canvasInnerShadowEffect.d.ts +6 -0
- package/dist/canvasInnerShadowEffect.d.ts.map +1 -0
- package/dist/canvasInnerShadowEffect.js +43 -0
- package/dist/canvasInnerShadowEffect.js.map +1 -0
- package/dist/canvasOuterGlowEffect.d.ts +2 -1
- package/dist/canvasOuterGlowEffect.d.ts.map +1 -1
- package/dist/canvasOuterGlowEffect.js +4 -0
- package/dist/canvasOuterGlowEffect.js.map +1 -1
- package/dist/canvasPixelateEffect.d.ts +2 -1
- package/dist/canvasPixelateEffect.d.ts.map +1 -1
- package/dist/canvasPixelateEffect.js +4 -0
- package/dist/canvasPixelateEffect.js.map +1 -1
- package/dist/canvasRenderEffectPipeline.d.ts.map +1 -1
- package/dist/canvasRenderEffectPipeline.js +9 -3
- package/dist/canvasRenderEffectPipeline.js.map +1 -1
- package/dist/canvasRenderEffectRegistry.d.ts.map +1 -1
- package/dist/canvasRenderEffectRegistry.js +9 -12
- package/dist/canvasRenderEffectRegistry.js.map +1 -1
- package/dist/canvasRenderTextureEffect.d.ts +3 -0
- package/dist/canvasRenderTextureEffect.d.ts.map +1 -0
- package/dist/canvasRenderTextureEffect.js +31 -0
- package/dist/canvasRenderTextureEffect.js.map +1 -0
- package/dist/canvasScanlinesEffect.d.ts +2 -1
- package/dist/canvasScanlinesEffect.d.ts.map +1 -1
- package/dist/canvasScanlinesEffect.js +4 -0
- package/dist/canvasScanlinesEffect.js.map +1 -1
- package/dist/canvasSourceModeCompositing.d.ts +1 -0
- package/dist/canvasSourceModeCompositing.d.ts.map +1 -1
- package/dist/canvasSourceModeCompositing.js +24 -0
- package/dist/canvasSourceModeCompositing.js.map +1 -1
- package/dist/canvasVignetteEffect.d.ts +2 -1
- package/dist/canvasVignetteEffect.d.ts.map +1 -1
- package/dist/canvasVignetteEffect.js +4 -0
- package/dist/canvasVignetteEffect.js.map +1 -1
- package/dist/contract.d.ts +9 -31
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -31
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
- package/src/canvasBevelEffect.test.ts +212 -0
- package/src/canvasBlendEffect.test.ts +235 -0
- package/src/canvasBloomEffect.test.ts +9 -1
- package/src/canvasBlurEffect.test.ts +12 -1
- package/src/canvasColorMatrixPass.test.ts +61 -11
- package/src/canvasCompositeEffect.test.ts +181 -0
- package/src/canvasDropShadowEffect.test.ts +15 -8
- package/src/canvasEffectRegistration.test.ts +27 -0
- package/src/canvasFilmGrainEffect.test.ts +9 -1
- package/src/canvasGradientBevelEffect.test.ts +180 -0
- package/src/canvasGradientGlowEffect.test.ts +125 -0
- package/src/canvasGradientRamp.test.ts +98 -0
- package/src/canvasInnerGlowEffect.test.ts +152 -0
- package/src/canvasInnerShadowEffect.test.ts +173 -0
- package/src/canvasOuterGlowEffect.test.ts +15 -8
- package/src/canvasPixelateEffect.test.ts +9 -1
- package/src/canvasRealizedEffectRegistration.test.ts +30 -0
- package/src/canvasRenderEffectPipeline.test.ts +38 -0
- package/src/canvasRenderEffectRegistry.test.ts +10 -9
- package/src/canvasRenderTextureEffect.test.ts +73 -0
- package/src/canvasScanlinesEffect.test.ts +9 -1
- package/src/canvasSourceModeCompositing.test.ts +40 -0
- package/src/canvasVignetteEffect.test.ts +9 -1
- package/dist/canvasBokehDepthOfFieldEffect.d.ts +0 -4
- package/dist/canvasBokehDepthOfFieldEffect.d.ts.map +0 -1
- package/dist/canvasBokehDepthOfFieldEffect.js +0 -10
- package/dist/canvasBokehDepthOfFieldEffect.js.map +0 -1
- package/dist/canvasCameraMotionBlurEffect.d.ts +0 -4
- package/dist/canvasCameraMotionBlurEffect.d.ts.map +0 -1
- package/dist/canvasCameraMotionBlurEffect.js +0 -11
- package/dist/canvasCameraMotionBlurEffect.js.map +0 -1
- package/dist/canvasChromaticAberrationEffect.d.ts +0 -4
- package/dist/canvasChromaticAberrationEffect.d.ts.map +0 -1
- package/dist/canvasChromaticAberrationEffect.js +0 -11
- package/dist/canvasChromaticAberrationEffect.js.map +0 -1
- package/dist/canvasConvolutionEffect.d.ts +0 -4
- package/dist/canvasConvolutionEffect.d.ts.map +0 -1
- package/dist/canvasConvolutionEffect.js +0 -11
- package/dist/canvasConvolutionEffect.js.map +0 -1
- package/dist/canvasCrtEffect.d.ts +0 -4
- package/dist/canvasCrtEffect.d.ts.map +0 -1
- package/dist/canvasCrtEffect.js +0 -11
- package/dist/canvasCrtEffect.js.map +0 -1
- package/dist/canvasDirectionalBlurEffect.d.ts +0 -4
- package/dist/canvasDirectionalBlurEffect.d.ts.map +0 -1
- package/dist/canvasDirectionalBlurEffect.js +0 -11
- package/dist/canvasDirectionalBlurEffect.js.map +0 -1
- package/dist/canvasDisplacementEffect.d.ts +0 -4
- package/dist/canvasDisplacementEffect.d.ts.map +0 -1
- package/dist/canvasDisplacementEffect.js +0 -10
- package/dist/canvasDisplacementEffect.js.map +0 -1
- package/dist/canvasDitherEffect.d.ts +0 -4
- package/dist/canvasDitherEffect.d.ts.map +0 -1
- package/dist/canvasDitherEffect.js +0 -11
- package/dist/canvasDitherEffect.js.map +0 -1
- package/dist/canvasFxaaEffect.d.ts +0 -4
- package/dist/canvasFxaaEffect.d.ts.map +0 -1
- package/dist/canvasFxaaEffect.js +0 -11
- package/dist/canvasFxaaEffect.js.map +0 -1
- package/dist/canvasGlitchEffect.d.ts +0 -4
- package/dist/canvasGlitchEffect.d.ts.map +0 -1
- package/dist/canvasGlitchEffect.js +0 -11
- package/dist/canvasGlitchEffect.js.map +0 -1
- package/dist/canvasGodRaysEffect.d.ts +0 -4
- package/dist/canvasGodRaysEffect.d.ts.map +0 -1
- package/dist/canvasGodRaysEffect.js +0 -11
- package/dist/canvasGodRaysEffect.js.map +0 -1
- package/dist/canvasHalftoneEffect.d.ts +0 -4
- package/dist/canvasHalftoneEffect.d.ts.map +0 -1
- package/dist/canvasHalftoneEffect.js +0 -10
- package/dist/canvasHalftoneEffect.js.map +0 -1
- package/dist/canvasKuwaharaEffect.d.ts +0 -4
- package/dist/canvasKuwaharaEffect.d.ts.map +0 -1
- package/dist/canvasKuwaharaEffect.js +0 -11
- package/dist/canvasKuwaharaEffect.js.map +0 -1
- package/dist/canvasLensDirtEffect.d.ts +0 -4
- package/dist/canvasLensDirtEffect.d.ts.map +0 -1
- package/dist/canvasLensDirtEffect.js +0 -11
- package/dist/canvasLensDirtEffect.js.map +0 -1
- package/dist/canvasLensDistortionEffect.d.ts +0 -4
- package/dist/canvasLensDistortionEffect.d.ts.map +0 -1
- package/dist/canvasLensDistortionEffect.js +0 -10
- package/dist/canvasLensDistortionEffect.js.map +0 -1
- package/dist/canvasLensFlareEffect.d.ts +0 -4
- package/dist/canvasLensFlareEffect.d.ts.map +0 -1
- package/dist/canvasLensFlareEffect.js +0 -11
- package/dist/canvasLensFlareEffect.js.map +0 -1
- package/dist/canvasMedianEffect.d.ts +0 -4
- package/dist/canvasMedianEffect.d.ts.map +0 -1
- package/dist/canvasMedianEffect.js +0 -11
- package/dist/canvasMedianEffect.js.map +0 -1
- package/dist/canvasMotionBlurEffect.d.ts +0 -4
- package/dist/canvasMotionBlurEffect.d.ts.map +0 -1
- package/dist/canvasMotionBlurEffect.js +0 -11
- package/dist/canvasMotionBlurEffect.js.map +0 -1
- package/dist/canvasOutlineEffect.d.ts +0 -4
- package/dist/canvasOutlineEffect.d.ts.map +0 -1
- package/dist/canvasOutlineEffect.js +0 -11
- package/dist/canvasOutlineEffect.js.map +0 -1
- package/dist/canvasPosterizeEffect.d.ts +0 -4
- package/dist/canvasPosterizeEffect.d.ts.map +0 -1
- package/dist/canvasPosterizeEffect.js +0 -10
- package/dist/canvasPosterizeEffect.js.map +0 -1
- package/dist/canvasRadialBlurEffect.d.ts +0 -4
- package/dist/canvasRadialBlurEffect.d.ts.map +0 -1
- package/dist/canvasRadialBlurEffect.js +0 -11
- package/dist/canvasRadialBlurEffect.js.map +0 -1
- package/dist/canvasScreenSpaceFogEffect.d.ts +0 -4
- package/dist/canvasScreenSpaceFogEffect.d.ts.map +0 -1
- package/dist/canvasScreenSpaceFogEffect.js +0 -10
- package/dist/canvasScreenSpaceFogEffect.js.map +0 -1
- package/dist/canvasSharpenEffect.d.ts +0 -4
- package/dist/canvasSharpenEffect.d.ts.map +0 -1
- package/dist/canvasSharpenEffect.js +0 -11
- package/dist/canvasSharpenEffect.js.map +0 -1
- package/dist/canvasSketchEffect.d.ts +0 -4
- package/dist/canvasSketchEffect.d.ts.map +0 -1
- package/dist/canvasSketchEffect.js +0 -11
- package/dist/canvasSketchEffect.js.map +0 -1
- package/dist/canvasSmaaEffect.d.ts +0 -4
- package/dist/canvasSmaaEffect.d.ts.map +0 -1
- package/dist/canvasSmaaEffect.js +0 -11
- package/dist/canvasSmaaEffect.js.map +0 -1
- package/dist/canvasSsaoEffect.d.ts +0 -4
- package/dist/canvasSsaoEffect.d.ts.map +0 -1
- package/dist/canvasSsaoEffect.js +0 -11
- package/dist/canvasSsaoEffect.js.map +0 -1
- package/dist/canvasSsrEffect.d.ts +0 -4
- package/dist/canvasSsrEffect.d.ts.map +0 -1
- package/dist/canvasSsrEffect.js +0 -11
- package/dist/canvasSsrEffect.js.map +0 -1
- package/dist/canvasTaaEffect.d.ts +0 -4
- package/dist/canvasTaaEffect.d.ts.map +0 -1
- package/dist/canvasTaaEffect.js +0 -11
- package/dist/canvasTaaEffect.js.map +0 -1
- package/dist/canvasTiltShiftEffect.d.ts +0 -4
- package/dist/canvasTiltShiftEffect.d.ts.map +0 -1
- package/dist/canvasTiltShiftEffect.js +0 -11
- package/dist/canvasTiltShiftEffect.js.map +0 -1
- package/dist/canvasToneMapEffect.d.ts +0 -4
- package/dist/canvasToneMapEffect.d.ts.map +0 -1
- package/dist/canvasToneMapEffect.js +0 -12
- package/dist/canvasToneMapEffect.js.map +0 -1
- package/dist/canvasWhiteBalanceEffect.d.ts +0 -4
- package/dist/canvasWhiteBalanceEffect.d.ts.map +0 -1
- package/dist/canvasWhiteBalanceEffect.js +0 -11
- package/dist/canvasWhiteBalanceEffect.js.map +0 -1
- package/src/canvasBokehDepthOfFieldEffect.test.ts +0 -16
- package/src/canvasCameraMotionBlurEffect.test.ts +0 -16
- package/src/canvasChromaticAberrationEffect.test.ts +0 -16
- package/src/canvasConvolutionEffect.test.ts +0 -13
- package/src/canvasCrtEffect.test.ts +0 -13
- package/src/canvasDirectionalBlurEffect.test.ts +0 -16
- package/src/canvasDisplacementEffect.test.ts +0 -13
- package/src/canvasDitherEffect.test.ts +0 -13
- package/src/canvasFxaaEffect.test.ts +0 -13
- package/src/canvasGlitchEffect.test.ts +0 -13
- package/src/canvasGodRaysEffect.test.ts +0 -13
- package/src/canvasHalftoneEffect.test.ts +0 -13
- package/src/canvasKuwaharaEffect.test.ts +0 -13
- package/src/canvasLensDirtEffect.test.ts +0 -13
- package/src/canvasLensDistortionEffect.test.ts +0 -16
- package/src/canvasLensFlareEffect.test.ts +0 -13
- package/src/canvasMedianEffect.test.ts +0 -13
- package/src/canvasMotionBlurEffect.test.ts +0 -13
- package/src/canvasOutlineEffect.test.ts +0 -13
- package/src/canvasPosterizeEffect.test.ts +0 -13
- package/src/canvasRadialBlurEffect.test.ts +0 -13
- package/src/canvasScreenSpaceFogEffect.test.ts +0 -16
- package/src/canvasSharpenEffect.test.ts +0 -13
- package/src/canvasSketchEffect.test.ts +0 -13
- package/src/canvasSmaaEffect.test.ts +0 -13
- package/src/canvasSsaoEffect.test.ts +0 -13
- package/src/canvasSsrEffect.test.ts +0 -13
- package/src/canvasTaaEffect.test.ts +0 -13
- package/src/canvasTiltShiftEffect.test.ts +0 -13
- package/src/canvasToneMapEffect.test.ts +0 -13
- package/src/canvasWhiteBalanceEffect.test.ts +0 -13
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createCanvasRenderState, createCanvasRenderTarget } from '@flighthq/scene2d-canvas/contract';
|
|
2
|
+
import type { CanvasRenderTarget, CanvasRenderTargetPool, InnerShadowEffect } from '@flighthq/types/contract';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
applyInnerShadowEffectToCanvas,
|
|
6
|
+
defaultCanvasInnerShadowEffectRunner,
|
|
7
|
+
registerCanvasInnerShadowEffect,
|
|
8
|
+
} from './canvasInnerShadowEffect';
|
|
9
|
+
import { getCanvasRenderEffectRunner } from './canvasRenderEffectRegistry';
|
|
10
|
+
|
|
11
|
+
// Recipe assertions rather than pixels — see canvasBlendEffect.test.ts for why jsdom forces that. Scratch
|
|
12
|
+
// targets are pre-seeded into the pool so each pass is identifiable; acquisition pops from the end, so the
|
|
13
|
+
// seed order is the reverse of the acquisition order.
|
|
14
|
+
function seededPool(ids: readonly string[]): { pool: CanvasRenderTargetPool; targets: CanvasRenderTarget[] } {
|
|
15
|
+
const targets = ids.map((id) => {
|
|
16
|
+
const target = createCanvasRenderTarget(4, 4);
|
|
17
|
+
target.canvas.id = id;
|
|
18
|
+
return target;
|
|
19
|
+
});
|
|
20
|
+
return { pool: { free: [...targets].reverse(), inUse: [] }, targets };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Draw {
|
|
24
|
+
entry: string;
|
|
25
|
+
dx: number;
|
|
26
|
+
dy: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function recordAll(log: Draw[], targets: readonly Readonly<CanvasRenderTarget>[]): void {
|
|
30
|
+
for (const target of targets) {
|
|
31
|
+
const context = target.context;
|
|
32
|
+
const into = target.canvas.id;
|
|
33
|
+
vi.spyOn(context, 'drawImage').mockImplementation(((image: CanvasImageSource, dx?: number, dy?: number) => {
|
|
34
|
+
const from = (image as HTMLCanvasElement).id || 'canvas';
|
|
35
|
+
log.push({ entry: `${from}->${into}|${context.globalCompositeOperation}`, dx: dx ?? 0, dy: dy ?? 0 });
|
|
36
|
+
}) as typeof context.drawImage);
|
|
37
|
+
vi.spyOn(context, 'fillRect').mockImplementation((() => {
|
|
38
|
+
log.push({ entry: `fill->${into}|${context.globalCompositeOperation}`, dx: 0, dy: 0 });
|
|
39
|
+
}) as typeof context.fillRect);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function scene(): { source: CanvasRenderTarget; dest: CanvasRenderTarget } {
|
|
44
|
+
const source = createCanvasRenderTarget(4, 4);
|
|
45
|
+
const dest = createCanvasRenderTarget(4, 4);
|
|
46
|
+
source.canvas.id = 'source';
|
|
47
|
+
dest.canvas.id = 'dest';
|
|
48
|
+
return { source, dest };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function innerShadow(over: Partial<InnerShadowEffect> = {}): InnerShadowEffect {
|
|
52
|
+
return { kind: 'InnerShadowEffect', ...over } as InnerShadowEffect;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
vi.restoreAllMocks();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('applyInnerShadowEffectToCanvas', () => {
|
|
60
|
+
it('inverts, blurs, offsets, clips to the source, and draws over the source in that order', () => {
|
|
61
|
+
// Angle 0 so the offset is exactly (distance, 0) and the ordering assertion carries no float noise.
|
|
62
|
+
const { source, dest } = scene();
|
|
63
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
64
|
+
const log: Draw[] = [];
|
|
65
|
+
recordAll(log, [...targets, dest]);
|
|
66
|
+
|
|
67
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow({ angle: 0, distance: 4 }));
|
|
68
|
+
|
|
69
|
+
expect(log.map((d) => `${d.entry}|${d.dx},${d.dy}`)).toEqual([
|
|
70
|
+
'fill->mask|source-over|0,0',
|
|
71
|
+
'source->mask|destination-out|0,0',
|
|
72
|
+
'mask->blurred|source-over|0,0',
|
|
73
|
+
// Blur first, THEN offset. Offsetting the mask before blurring would smear the shadow
|
|
74
|
+
// symmetrically about the shifted edge and lose the directional falloff entirely.
|
|
75
|
+
'blurred->shadow|source-over|4,0',
|
|
76
|
+
'source->shadow|destination-in|0,0',
|
|
77
|
+
'source->dest|source-over|0,0',
|
|
78
|
+
'shadow->dest|source-over|0,0',
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('converts the angle from degrees when placing the offset', () => {
|
|
83
|
+
// The descriptor is authoring-layer, so its angle is degrees; treating it as radians would put a
|
|
84
|
+
// 90-degree shadow at roughly (-1.7, 3.6) instead of straight down.
|
|
85
|
+
const { source, dest } = scene();
|
|
86
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
87
|
+
const log: Draw[] = [];
|
|
88
|
+
recordAll(log, [...targets, dest]);
|
|
89
|
+
|
|
90
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow({ angle: 90, distance: 4 }));
|
|
91
|
+
|
|
92
|
+
const offset = log.find((d) => d.entry.startsWith('blurred->shadow'));
|
|
93
|
+
expect(offset?.dx).toBeCloseTo(0);
|
|
94
|
+
expect(offset?.dy).toBeCloseTo(4);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('offsets into a separate target rather than onto the blurred one', () => {
|
|
98
|
+
// Drawing a target onto itself at an offset reads pixels it is concurrently writing. The third
|
|
99
|
+
// scratch target exists for exactly this, so the shift must land somewhere other than `blurred`.
|
|
100
|
+
const { source, dest } = scene();
|
|
101
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
102
|
+
const log: Draw[] = [];
|
|
103
|
+
recordAll(log, [...targets, dest]);
|
|
104
|
+
|
|
105
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow({ angle: 0, distance: 4 }));
|
|
106
|
+
|
|
107
|
+
expect(log.some((d) => d.entry === 'blurred->blurred|source-over')).toBe(false);
|
|
108
|
+
expect(log.some((d) => d.entry === 'blurred->shadow|source-over')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('drops the source and keeps only the shadow when sourceMode is hide', () => {
|
|
112
|
+
const { source, dest } = scene();
|
|
113
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
114
|
+
const log: Draw[] = [];
|
|
115
|
+
recordAll(log, [...targets, dest]);
|
|
116
|
+
|
|
117
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow({ sourceMode: 'hide' }));
|
|
118
|
+
|
|
119
|
+
expect(log.filter((d) => d.entry.endsWith('->dest|source-over')).map((d) => d.entry)).toEqual([
|
|
120
|
+
'shadow->dest|source-over',
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('draws the shadow once per whole unit of strength', () => {
|
|
125
|
+
const { source, dest } = scene();
|
|
126
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
127
|
+
const log: Draw[] = [];
|
|
128
|
+
recordAll(log, [...targets, dest]);
|
|
129
|
+
|
|
130
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow({ strength: 3 }));
|
|
131
|
+
|
|
132
|
+
expect(log.filter((d) => d.entry.startsWith('shadow->dest'))).toHaveLength(3);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns all three scratch targets to the pool', () => {
|
|
136
|
+
const { source, dest } = scene();
|
|
137
|
+
const { pool } = seededPool(['mask', 'blurred', 'shadow']);
|
|
138
|
+
|
|
139
|
+
applyInnerShadowEffectToCanvas(source, dest, pool, innerShadow());
|
|
140
|
+
|
|
141
|
+
expect(pool.inUse).toHaveLength(0);
|
|
142
|
+
expect(pool.free).toHaveLength(3);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('runs without a caller-supplied pool', () => {
|
|
146
|
+
const { source, dest } = scene();
|
|
147
|
+
|
|
148
|
+
expect(() => applyInnerShadowEffectToCanvas(source, dest, innerShadow())).not.toThrow();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('defaultCanvasInnerShadowEffectRunner', () => {
|
|
153
|
+
it('applies the shadow through the pipeline context', () => {
|
|
154
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
155
|
+
const { source, dest } = scene();
|
|
156
|
+
const { pool, targets } = seededPool(['mask', 'blurred', 'shadow']);
|
|
157
|
+
const log: Draw[] = [];
|
|
158
|
+
recordAll(log, [...targets, dest]);
|
|
159
|
+
|
|
160
|
+
defaultCanvasInnerShadowEffectRunner({ state, source, dest, pool }, innerShadow());
|
|
161
|
+
|
|
162
|
+
expect(log.map((d) => d.entry)).toContain('source->shadow|destination-in');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('registerCanvasInnerShadowEffect', () => {
|
|
167
|
+
it('registers the default runner under the InnerShadowEffect kind', () => {
|
|
168
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
169
|
+
registerCanvasInnerShadowEffect(state);
|
|
170
|
+
|
|
171
|
+
expect(getCanvasRenderEffectRunner(state, 'InnerShadowEffect')).toBe(defaultCanvasInnerShadowEffectRunner);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -29,7 +29,11 @@ vi.mock('./canvasSourceModeCompositing', () => ({
|
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
import { drawCanvasEffectPass } from './canvasEffectCompositing';
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
applyOuterGlowEffectToCanvas,
|
|
34
|
+
defaultCanvasOuterGlowEffectRunner,
|
|
35
|
+
registerCanvasOuterGlowEffect,
|
|
36
|
+
} from './canvasOuterGlowEffect';
|
|
33
37
|
import { compositeCanvasSourceMode } from './canvasSourceModeCompositing';
|
|
34
38
|
|
|
35
39
|
describe('applyOuterGlowEffectToCanvas', () => {
|
|
@@ -72,17 +76,20 @@ describe('defaultCanvasOuterGlowEffectRunner', () => {
|
|
|
72
76
|
});
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
describe('registerCanvasOuterGlowEffect', () => {
|
|
80
|
+
it('is a function', () => expect(registerCanvasOuterGlowEffect).toBeTypeOf('function'));
|
|
77
81
|
});
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
vi.
|
|
81
|
-
vi.doUnmock('./canvasRenderEffectPipeline');
|
|
82
|
-
vi.doUnmock('./canvasSourceModeCompositing');
|
|
83
|
-
vi.resetModules();
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
vi.clearAllMocks();
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
function createTarget(id: string, width = 32, height = 16): never {
|
|
87
88
|
return { id, canvas: {}, context: {}, width, height } as never;
|
|
88
89
|
}
|
|
90
|
+
|
|
91
|
+
describe('registerCanvasOuterGlowEffect', () => {
|
|
92
|
+
it('is a function', () => {
|
|
93
|
+
expect(typeof registerCanvasOuterGlowEffect).toBe('function');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
applyPixelateEffectToCanvas,
|
|
3
|
+
defaultCanvasPixelateEffectRunner,
|
|
4
|
+
registerCanvasPixelateEffect,
|
|
5
|
+
} from './canvasPixelateEffect';
|
|
2
6
|
|
|
3
7
|
describe('applyPixelateEffectToCanvas', () => {
|
|
4
8
|
it('is a function', () => {
|
|
@@ -11,3 +15,7 @@ describe('defaultCanvasPixelateEffectRunner', () => {
|
|
|
11
15
|
expect(typeof defaultCanvasPixelateEffectRunner).toBe('function');
|
|
12
16
|
});
|
|
13
17
|
});
|
|
18
|
+
|
|
19
|
+
describe('registerCanvasPixelateEffect', () => {
|
|
20
|
+
it('is a function', () => expect(registerCanvasPixelateEffect).toBeTypeOf('function'));
|
|
21
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
|
|
2
|
+
|
|
3
|
+
import { defaultCanvasBloomEffectRunner, registerCanvasBloomEffect } from './canvasBloomEffect';
|
|
4
|
+
import { defaultCanvasBlurEffectRunner, registerCanvasBlurEffect } from './canvasBlurEffect';
|
|
5
|
+
import { defaultCanvasDropShadowEffectRunner, registerCanvasDropShadowEffect } from './canvasDropShadowEffect';
|
|
6
|
+
import { defaultCanvasFilmGrainEffectRunner, registerCanvasFilmGrainEffect } from './canvasFilmGrainEffect';
|
|
7
|
+
import { defaultCanvasOuterGlowEffectRunner, registerCanvasOuterGlowEffect } from './canvasOuterGlowEffect';
|
|
8
|
+
import { defaultCanvasPixelateEffectRunner, registerCanvasPixelateEffect } from './canvasPixelateEffect';
|
|
9
|
+
import { getCanvasRenderEffectRunner } from './canvasRenderEffectRegistry';
|
|
10
|
+
import { defaultCanvasScanlinesEffectRunner, registerCanvasScanlinesEffect } from './canvasScanlinesEffect';
|
|
11
|
+
import { defaultCanvasVignetteEffectRunner, registerCanvasVignetteEffect } from './canvasVignetteEffect';
|
|
12
|
+
|
|
13
|
+
const CASES = [
|
|
14
|
+
['BloomEffect', registerCanvasBloomEffect, defaultCanvasBloomEffectRunner],
|
|
15
|
+
['BlurEffect', registerCanvasBlurEffect, defaultCanvasBlurEffectRunner],
|
|
16
|
+
['DropShadowEffect', registerCanvasDropShadowEffect, defaultCanvasDropShadowEffectRunner],
|
|
17
|
+
['FilmGrainEffect', registerCanvasFilmGrainEffect, defaultCanvasFilmGrainEffectRunner],
|
|
18
|
+
['OuterGlowEffect', registerCanvasOuterGlowEffect, defaultCanvasOuterGlowEffectRunner],
|
|
19
|
+
['PixelateEffect', registerCanvasPixelateEffect, defaultCanvasPixelateEffectRunner],
|
|
20
|
+
['ScanlinesEffect', registerCanvasScanlinesEffect, defaultCanvasScanlinesEffectRunner],
|
|
21
|
+
['VignetteEffect', registerCanvasVignetteEffect, defaultCanvasVignetteEffectRunner],
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
describe('realized Canvas effect registration', () => {
|
|
25
|
+
it.each(CASES)('maps %s to its leaf runner', (kind, register, runner) => {
|
|
26
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
27
|
+
register(state);
|
|
28
|
+
expect(getCanvasRenderEffectRunner(state, kind)).toBe(runner);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
|
|
2
|
+
import type { CanvasRenderEffectRunner, RenderEffect } from '@flighthq/types/contract';
|
|
3
|
+
|
|
4
|
+
import { drawCanvasEffectPass } from './canvasEffectCompositing';
|
|
1
5
|
import {
|
|
2
6
|
acquireCanvasRenderTarget,
|
|
3
7
|
beginCanvasRenderEffectPipeline,
|
|
@@ -7,6 +11,7 @@ import {
|
|
|
7
11
|
endCanvasRenderEffectPipeline,
|
|
8
12
|
releaseCanvasRenderTarget,
|
|
9
13
|
} from './canvasRenderEffectPipeline';
|
|
14
|
+
import { registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
|
|
10
15
|
|
|
11
16
|
describe('acquireCanvasRenderTarget', () => {
|
|
12
17
|
it('is a function', () => {
|
|
@@ -48,6 +53,39 @@ describe('endCanvasRenderEffectPipeline', () => {
|
|
|
48
53
|
it('is a function', () => {
|
|
49
54
|
expect(typeof endCanvasRenderEffectPipeline).toBe('function');
|
|
50
55
|
});
|
|
56
|
+
|
|
57
|
+
it('writes an unregistered effect destination before chaining and presenting it', () => {
|
|
58
|
+
const canvas = document.createElement('canvas');
|
|
59
|
+
canvas.width = 4;
|
|
60
|
+
canvas.height = 4;
|
|
61
|
+
const state = createCanvasRenderState(canvas);
|
|
62
|
+
const pipeline = createCanvasRenderEffectPipeline(state);
|
|
63
|
+
beginCanvasRenderEffectPipeline(state, pipeline);
|
|
64
|
+
const scene = pipeline.sceneTarget!;
|
|
65
|
+
scene.context.fillStyle = '#ff0000';
|
|
66
|
+
scene.context.fillRect(0, 0, 4, 4);
|
|
67
|
+
|
|
68
|
+
const realizedRunner = vi.fn<CanvasRenderEffectRunner>((ctx) => {
|
|
69
|
+
drawCanvasEffectPass(ctx.dest, ctx.source, 'none');
|
|
70
|
+
});
|
|
71
|
+
registerCanvasRenderEffect(state, 'RealizedEffect', realizedRunner);
|
|
72
|
+
|
|
73
|
+
endCanvasRenderEffectPipeline(state, pipeline, [
|
|
74
|
+
{ kind: 'UnregisteredEffect' },
|
|
75
|
+
{ kind: 'RealizedEffect' },
|
|
76
|
+
] as RenderEffect[]);
|
|
77
|
+
|
|
78
|
+
const [unregisteredDest, realizedDest] = pipeline.pool.free;
|
|
79
|
+
expect(unregisteredDest).toBeDefined();
|
|
80
|
+
expect(realizedDest).toBeDefined();
|
|
81
|
+
expect(unregisteredDest!.context.drawImage).toHaveBeenCalledWith(scene.canvas, 0, 0);
|
|
82
|
+
expect(realizedRunner).toHaveBeenCalledWith(
|
|
83
|
+
expect.objectContaining({ source: unregisteredDest, dest: realizedDest }),
|
|
84
|
+
{ kind: 'RealizedEffect' },
|
|
85
|
+
);
|
|
86
|
+
expect(realizedDest!.context.drawImage).toHaveBeenCalledWith(unregisteredDest!.canvas, 0, 0);
|
|
87
|
+
expect(state.context.drawImage).toHaveBeenCalledWith(realizedDest!.canvas, 0, 0);
|
|
88
|
+
});
|
|
51
89
|
});
|
|
52
90
|
|
|
53
91
|
describe('releaseCanvasRenderTarget', () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createCanvasRenderState } from '@flighthq/scene2d-canvas/contract';
|
|
2
|
+
import type { CanvasRenderEffectRunner } from '@flighthq/types/contract';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
getCanvasRenderEffectRunner,
|
|
@@ -17,20 +18,20 @@ describe('hasCanvasRenderEffectRunner', () => {
|
|
|
17
18
|
expect(typeof hasCanvasRenderEffectRunner).toBe('function');
|
|
18
19
|
});
|
|
19
20
|
it('returns false when state has no registered runners', () => {
|
|
20
|
-
const
|
|
21
|
-
expect(hasCanvasRenderEffectRunner(
|
|
21
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
22
|
+
expect(hasCanvasRenderEffectRunner(state, 'NotRegisteredEffect')).toBe(false);
|
|
22
23
|
});
|
|
23
24
|
it('returns true after registering a runner for the given kind', () => {
|
|
24
|
-
const
|
|
25
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
25
26
|
const fakeRunner = (() => {}) as unknown as CanvasRenderEffectRunner;
|
|
26
|
-
registerCanvasRenderEffect(
|
|
27
|
-
expect(hasCanvasRenderEffectRunner(
|
|
27
|
+
registerCanvasRenderEffect(state, 'HasTestEffect', fakeRunner);
|
|
28
|
+
expect(hasCanvasRenderEffectRunner(state, 'HasTestEffect')).toBe(true);
|
|
28
29
|
});
|
|
29
30
|
it('returns false for a different kind on the same state', () => {
|
|
30
|
-
const
|
|
31
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
31
32
|
const fakeRunner = (() => {}) as unknown as CanvasRenderEffectRunner;
|
|
32
|
-
registerCanvasRenderEffect(
|
|
33
|
-
expect(hasCanvasRenderEffectRunner(
|
|
33
|
+
registerCanvasRenderEffect(state, 'OnlyThisEffect', fakeRunner);
|
|
34
|
+
expect(hasCanvasRenderEffectRunner(state, 'OtherEffect')).toBe(false);
|
|
34
35
|
});
|
|
35
36
|
});
|
|
36
37
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireCanvasRenderTexture,
|
|
3
|
+
createCanvasOffscreenRenderState,
|
|
4
|
+
createCanvasRenderState,
|
|
5
|
+
createCanvasRenderTexturePool,
|
|
6
|
+
copyCanvasRenderStateRegistrations,
|
|
7
|
+
getCanvasRenderTextureTarget,
|
|
8
|
+
isCanvasRenderTextureReady,
|
|
9
|
+
writeCanvasRenderTextureTarget,
|
|
10
|
+
} from '@flighthq/scene2d-canvas/contract';
|
|
11
|
+
import type { CanvasRenderEffectRunner } from '@flighthq/types/contract';
|
|
12
|
+
|
|
13
|
+
import { getCanvasRenderEffectRunner, registerCanvasRenderEffect } from './canvasRenderEffectRegistry';
|
|
14
|
+
import { applyCanvasRenderEffectsToRenderTexture } from './canvasRenderTextureEffect';
|
|
15
|
+
|
|
16
|
+
describe('applyCanvasRenderEffectsToRenderTexture', () => {
|
|
17
|
+
it('ping-pongs an even registered chain so the last operation publishes destination', () => {
|
|
18
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
19
|
+
const pool = createCanvasRenderTexturePool();
|
|
20
|
+
const source = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
21
|
+
const dest = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
22
|
+
const scratch = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
23
|
+
writeCanvasRenderTextureTarget(state, source, () => {});
|
|
24
|
+
const first: CanvasRenderEffectRunner = vi.fn();
|
|
25
|
+
const second: CanvasRenderEffectRunner = vi.fn();
|
|
26
|
+
registerCanvasRenderEffect(state, 'acme.First', first);
|
|
27
|
+
registerCanvasRenderEffect(state, 'acme.Second', second);
|
|
28
|
+
|
|
29
|
+
expect(
|
|
30
|
+
applyCanvasRenderEffectsToRenderTexture(state, pool, source, dest, scratch, [
|
|
31
|
+
{ kind: 'acme.First' },
|
|
32
|
+
{ kind: 'acme.Second' },
|
|
33
|
+
]),
|
|
34
|
+
).toBe(true);
|
|
35
|
+
|
|
36
|
+
expect(first).toHaveBeenCalledOnce();
|
|
37
|
+
expect(second).toHaveBeenCalledOnce();
|
|
38
|
+
expect(vi.mocked(first).mock.calls[0][0].dest).toBe(getCanvasRenderTextureTarget(state, scratch));
|
|
39
|
+
expect(vi.mocked(second).mock.calls[0][0].dest).toBe(getCanvasRenderTextureTarget(state, dest));
|
|
40
|
+
expect(isCanvasRenderTextureReady(state, dest)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('leaves destination unpublished when no effect kind is registered', () => {
|
|
44
|
+
const state = createCanvasRenderState(document.createElement('canvas'));
|
|
45
|
+
const pool = createCanvasRenderTexturePool();
|
|
46
|
+
const source = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
47
|
+
const dest = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
48
|
+
const scratch = acquireCanvasRenderTexture(state, pool, { width: 8, height: 8 });
|
|
49
|
+
writeCanvasRenderTextureTarget(state, source, () => {});
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
applyCanvasRenderEffectsToRenderTexture(state, pool, source, dest, scratch, [{ kind: 'acme.Missing' }]),
|
|
53
|
+
).toBe(false);
|
|
54
|
+
expect(isCanvasRenderTextureReady(state, dest)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('offscreen effect registration snapshots', () => {
|
|
59
|
+
it('copies runners at derivation and requires explicit re-copy for later runners', () => {
|
|
60
|
+
const screen = createCanvasRenderState(document.createElement('canvas'));
|
|
61
|
+
const first: CanvasRenderEffectRunner = vi.fn();
|
|
62
|
+
const later: CanvasRenderEffectRunner = vi.fn();
|
|
63
|
+
registerCanvasRenderEffect(screen, 'acme.First', first);
|
|
64
|
+
const offscreen = createCanvasOffscreenRenderState(screen);
|
|
65
|
+
registerCanvasRenderEffect(screen, 'acme.Later', later);
|
|
66
|
+
|
|
67
|
+
expect(getCanvasRenderEffectRunner(offscreen, 'acme.First')).toBe(first);
|
|
68
|
+
expect(getCanvasRenderEffectRunner(offscreen, 'acme.Later')).toBeNull();
|
|
69
|
+
|
|
70
|
+
copyCanvasRenderStateRegistrations(offscreen, screen);
|
|
71
|
+
expect(getCanvasRenderEffectRunner(offscreen, 'acme.Later')).toBe(later);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
applyScanlinesEffectToCanvas,
|
|
3
|
+
defaultCanvasScanlinesEffectRunner,
|
|
4
|
+
registerCanvasScanlinesEffect,
|
|
5
|
+
} from './canvasScanlinesEffect';
|
|
2
6
|
|
|
3
7
|
describe('applyScanlinesEffectToCanvas', () => {
|
|
4
8
|
it('is a function', () => {
|
|
@@ -11,3 +15,7 @@ describe('defaultCanvasScanlinesEffectRunner', () => {
|
|
|
11
15
|
expect(typeof defaultCanvasScanlinesEffectRunner).toBe('function');
|
|
12
16
|
});
|
|
13
17
|
});
|
|
18
|
+
|
|
19
|
+
describe('registerCanvasScanlinesEffect', () => {
|
|
20
|
+
it('is a function', () => expect(registerCanvasScanlinesEffect).toBeTypeOf('function'));
|
|
21
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { createCanvasRenderTarget } from '@flighthq/scene2d-canvas/contract';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
clearCanvasTarget,
|
|
3
5
|
compositeCanvasImage,
|
|
4
6
|
compositeCanvasSourceMode,
|
|
7
|
+
drawCanvasInvertedTintedAlphaMask,
|
|
5
8
|
drawCanvasTintedAlphaMask,
|
|
6
9
|
} from './canvasSourceModeCompositing';
|
|
7
10
|
|
|
@@ -23,6 +26,43 @@ describe('compositeCanvasSourceMode', () => {
|
|
|
23
26
|
});
|
|
24
27
|
});
|
|
25
28
|
|
|
29
|
+
describe('drawCanvasInvertedTintedAlphaMask', () => {
|
|
30
|
+
it('fills the whole target and then knocks the source out of it', () => {
|
|
31
|
+
// The inversion IS the knockout: fill everywhere, then remove where the silhouette is. Doing it the
|
|
32
|
+
// other way round (source-in against the source) produces the ordinary tinted mask, which is the
|
|
33
|
+
// outer-effect primitive and would turn every inner effect into an outer one.
|
|
34
|
+
const dest = createCanvasRenderTarget(4, 4);
|
|
35
|
+
const source = createCanvasRenderTarget(4, 4);
|
|
36
|
+
source.canvas.id = 'source';
|
|
37
|
+
const log: string[] = [];
|
|
38
|
+
vi.spyOn(dest.context, 'fillRect').mockImplementation((() => {
|
|
39
|
+
log.push(`fill|${dest.context.globalCompositeOperation}`);
|
|
40
|
+
}) as typeof dest.context.fillRect);
|
|
41
|
+
vi.spyOn(dest.context, 'drawImage').mockImplementation(((_i: CanvasImageSource, dx?: number, dy?: number) => {
|
|
42
|
+
log.push(`draw|${dest.context.globalCompositeOperation}|${dx ?? 0},${dy ?? 0}`);
|
|
43
|
+
}) as typeof dest.context.drawImage);
|
|
44
|
+
|
|
45
|
+
drawCanvasInvertedTintedAlphaMask(dest, source, 0x123456, 1, 1);
|
|
46
|
+
|
|
47
|
+
expect(log).toEqual(['fill|source-over', 'draw|destination-out|0,0']);
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('shifts which side of the boundary the tint survives on when given an offset', () => {
|
|
52
|
+
const dest = createCanvasRenderTarget(4, 4);
|
|
53
|
+
const source = createCanvasRenderTarget(4, 4);
|
|
54
|
+
let offset = '';
|
|
55
|
+
vi.spyOn(dest.context, 'drawImage').mockImplementation(((_i: CanvasImageSource, dx?: number, dy?: number) => {
|
|
56
|
+
offset = `${dx ?? 0},${dy ?? 0}`;
|
|
57
|
+
}) as typeof dest.context.drawImage);
|
|
58
|
+
|
|
59
|
+
drawCanvasInvertedTintedAlphaMask(dest, source, 0x123456, 1, 1, 3, -2);
|
|
60
|
+
|
|
61
|
+
expect(offset).toBe('3,-2');
|
|
62
|
+
vi.restoreAllMocks();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
26
66
|
describe('drawCanvasTintedAlphaMask', () => {
|
|
27
67
|
it('is a function', () => {
|
|
28
68
|
expect(typeof drawCanvasTintedAlphaMask).toBe('function');
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
applyVignetteEffectToCanvas,
|
|
3
|
+
defaultCanvasVignetteEffectRunner,
|
|
4
|
+
registerCanvasVignetteEffect,
|
|
5
|
+
} from './canvasVignetteEffect';
|
|
2
6
|
|
|
3
7
|
describe('applyVignetteEffectToCanvas', () => {
|
|
4
8
|
it('is a function', () => {
|
|
@@ -11,3 +15,7 @@ describe('defaultCanvasVignetteEffectRunner', () => {
|
|
|
11
15
|
expect(typeof defaultCanvasVignetteEffectRunner).toBe('function');
|
|
12
16
|
});
|
|
13
17
|
});
|
|
18
|
+
|
|
19
|
+
describe('registerCanvasVignetteEffect', () => {
|
|
20
|
+
it('is a function', () => expect(registerCanvasVignetteEffect).toBeTypeOf('function'));
|
|
21
|
+
});
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { BokehDepthOfFieldEffect, CanvasRenderEffectRunner, CanvasRenderTarget } from '@flighthq/types/contract';
|
|
2
|
-
export declare function applyBokehDepthOfFieldEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, _effect: Readonly<BokehDepthOfFieldEffect>): void;
|
|
3
|
-
export declare const defaultCanvasBokehDepthOfFieldEffectRunner: CanvasRenderEffectRunner;
|
|
4
|
-
//# sourceMappingURL=canvasBokehDepthOfFieldEffect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasBokehDepthOfFieldEffect.d.ts","sourceRoot":"","sources":["../src/canvasBokehDepthOfFieldEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMtH,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GACzC,IAAI,CAEN;AAED,eAAO,MAAM,0CAA0C,EAAE,wBAExD,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { passthroughCanvasEffectPass } from './canvasEffectCompositing';
|
|
2
|
-
// Bokeh depth-of-field (PASSTHROUGH): a per-pixel circle-of-confusion blur driven by a depth buffer.
|
|
3
|
-
// Genuinely unsupportable on Canvas 2D: needs a depth G-buffer the 2D context does not expose. Passthrough.
|
|
4
|
-
export function applyBokehDepthOfFieldEffectToCanvas(source, dest, _effect) {
|
|
5
|
-
passthroughCanvasEffectPass(dest, source);
|
|
6
|
-
}
|
|
7
|
-
export const defaultCanvasBokehDepthOfFieldEffectRunner = (ctx, effect) => {
|
|
8
|
-
applyBokehDepthOfFieldEffectToCanvas(ctx.source, ctx.dest, effect);
|
|
9
|
-
};
|
|
10
|
-
//# sourceMappingURL=canvasBokehDepthOfFieldEffect.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasBokehDepthOfFieldEffect.js","sourceRoot":"","sources":["../src/canvasBokehDepthOfFieldEffect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAExE,qGAAqG;AACrG,4GAA4G;AAC5G,MAAM,UAAU,oCAAoC,CAClD,MAAoC,EACpC,IAAkC,EAClC,OAA0C;IAE1C,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,0CAA0C,GAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IAClG,oCAAoC,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAiC,CAAC,CAAC;AAChG,CAAC,CAAC"}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { CameraMotionBlurEffect, CanvasRenderEffectRunner, CanvasRenderTarget } from '@flighthq/types/contract';
|
|
2
|
-
export declare function applyCameraMotionBlurEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, _effect: Readonly<CameraMotionBlurEffect>): void;
|
|
3
|
-
export declare const defaultCanvasCameraMotionBlurEffectRunner: CanvasRenderEffectRunner;
|
|
4
|
-
//# sourceMappingURL=canvasCameraMotionBlurEffect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasCameraMotionBlurEffect.d.ts","sourceRoot":"","sources":["../src/canvasCameraMotionBlurEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOrH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,sBAAsB,CAAC,GACxC,IAAI,CAEN;AAED,eAAO,MAAM,yCAAyC,EAAE,wBAEvD,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { passthroughCanvasEffectPass } from './canvasEffectCompositing';
|
|
2
|
-
// Camera3D motion blur (PASSTHROUGH): a zoom smear accumulating taps toward screen center.
|
|
3
|
-
// Genuinely unsupportable on Canvas 2D: needs camera velocity/depth reprojection the 2D context does not
|
|
4
|
-
// expose. Passthrough.
|
|
5
|
-
export function applyCameraMotionBlurEffectToCanvas(source, dest, _effect) {
|
|
6
|
-
passthroughCanvasEffectPass(dest, source);
|
|
7
|
-
}
|
|
8
|
-
export const defaultCanvasCameraMotionBlurEffectRunner = (ctx, effect) => {
|
|
9
|
-
applyCameraMotionBlurEffectToCanvas(ctx.source, ctx.dest, effect);
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=canvasCameraMotionBlurEffect.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasCameraMotionBlurEffect.js","sourceRoot":"","sources":["../src/canvasCameraMotionBlurEffect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAExE,2FAA2F;AAC3F,yGAAyG;AACzG,uBAAuB;AACvB,MAAM,UAAU,mCAAmC,CACjD,MAAoC,EACpC,IAAkC,EAClC,OAAyC;IAEzC,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,yCAAyC,GAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACjG,mCAAmC,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAgC,CAAC,CAAC;AAC9F,CAAC,CAAC"}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { CanvasRenderEffectRunner, CanvasRenderTarget, ChromaticAberrationEffect } from '@flighthq/types/contract';
|
|
2
|
-
export declare function applyChromaticAberrationEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, _effect: Readonly<ChromaticAberrationEffect>): void;
|
|
3
|
-
export declare const defaultCanvasChromaticAberrationEffectRunner: CanvasRenderEffectRunner;
|
|
4
|
-
//# sourceMappingURL=canvasChromaticAberrationEffect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasChromaticAberrationEffect.d.ts","sourceRoot":"","sources":["../src/canvasChromaticAberrationEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAOxH,wBAAgB,sCAAsC,CACpD,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,yBAAyB,CAAC,GAC3C,IAAI,CAEN;AAED,eAAO,MAAM,4CAA4C,EAAE,wBAE1D,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { passthroughCanvasEffectPass } from './canvasEffectCompositing';
|
|
2
|
-
// Chromatic aberration (PASSTHROUGH): splitting R/G/B channels at offset positions, which a 2D drawImage
|
|
3
|
-
// cannot address. No CSS filter equivalent, but expressible per-pixel via getImageData/putImageData; not
|
|
4
|
-
// yet implemented — passthrough for now.
|
|
5
|
-
export function applyChromaticAberrationEffectToCanvas(source, dest, _effect) {
|
|
6
|
-
passthroughCanvasEffectPass(dest, source);
|
|
7
|
-
}
|
|
8
|
-
export const defaultCanvasChromaticAberrationEffectRunner = (ctx, effect) => {
|
|
9
|
-
applyChromaticAberrationEffectToCanvas(ctx.source, ctx.dest, effect);
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=canvasChromaticAberrationEffect.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasChromaticAberrationEffect.js","sourceRoot":"","sources":["../src/canvasChromaticAberrationEffect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAExE,yGAAyG;AACzG,yGAAyG;AACzG,yCAAyC;AACzC,MAAM,UAAU,sCAAsC,CACpD,MAAoC,EACpC,IAAkC,EAClC,OAA4C;IAE5C,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,4CAA4C,GAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IACpG,sCAAsC,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAmC,CAAC,CAAC;AACpG,CAAC,CAAC"}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { CanvasRenderEffectRunner, CanvasRenderTarget, ConvolutionEffect } from '@flighthq/types/contract';
|
|
2
|
-
export declare function applyConvolutionEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, _effect: Readonly<ConvolutionEffect>): void;
|
|
3
|
-
export declare const defaultCanvasConvolutionEffectRunner: CanvasRenderEffectRunner;
|
|
4
|
-
//# sourceMappingURL=canvasConvolutionEffect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasConvolutionEffect.d.ts","sourceRoot":"","sources":["../src/canvasConvolutionEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAOhH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GACnC,IAAI,CAEN;AAED,eAAO,MAAM,oCAAoC,EAAE,wBAElD,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { passthroughCanvasEffectPass } from './canvasEffectCompositing';
|
|
2
|
-
// Convolution (PASSTHROUGH): a generic matrix-kernel gather is per-pixel neighbor math. No CSS filter
|
|
3
|
-
// equivalent, but expressible per-pixel via getImageData/putImageData; not yet implemented — passthrough
|
|
4
|
-
// for now.
|
|
5
|
-
export function applyConvolutionEffectToCanvas(source, dest, _effect) {
|
|
6
|
-
passthroughCanvasEffectPass(dest, source);
|
|
7
|
-
}
|
|
8
|
-
export const defaultCanvasConvolutionEffectRunner = (ctx, effect) => {
|
|
9
|
-
applyConvolutionEffectToCanvas(ctx.source, ctx.dest, effect);
|
|
10
|
-
};
|
|
11
|
-
//# sourceMappingURL=canvasConvolutionEffect.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasConvolutionEffect.js","sourceRoot":"","sources":["../src/canvasConvolutionEffect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAExE,sGAAsG;AACtG,yGAAyG;AACzG,WAAW;AACX,MAAM,UAAU,8BAA8B,CAC5C,MAAoC,EACpC,IAAkC,EAClC,OAAoC;IAEpC,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,oCAAoC,GAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;IAC5F,8BAA8B,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAA2B,CAAC,CAAC;AACpF,CAAC,CAAC"}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { CanvasRenderEffectRunner, CanvasRenderTarget, CrtEffect } from '@flighthq/types/contract';
|
|
2
|
-
export declare function applyCrtEffectToCanvas(source: Readonly<CanvasRenderTarget>, dest: Readonly<CanvasRenderTarget>, _effect: Readonly<CrtEffect>): void;
|
|
3
|
-
export declare const defaultCanvasCrtEffectRunner: CanvasRenderEffectRunner;
|
|
4
|
-
//# sourceMappingURL=canvasCrtEffect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvasCrtEffect.d.ts","sourceRoot":"","sources":["../src/canvasCrtEffect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAOxG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACpC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAC3B,IAAI,CAEN;AAED,eAAO,MAAM,4BAA4B,EAAE,wBAE1C,CAAC"}
|