@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.
- package/dist/canvasLensDistortionEffect.d.ts +5 -0
- package/dist/canvasLensDistortionEffect.d.ts.map +1 -0
- package/dist/canvasLensDistortionEffect.js +69 -0
- package/dist/canvasLensDistortionEffect.js.map +1 -0
- package/dist/canvasPosterizeEffect.d.ts +5 -0
- package/dist/canvasPosterizeEffect.d.ts.map +1 -0
- package/dist/canvasPosterizeEffect.js +35 -0
- package/dist/canvasPosterizeEffect.js.map +1 -0
- package/dist/canvasTiltShiftEffect.d.ts +5 -0
- package/dist/canvasTiltShiftEffect.d.ts.map +1 -0
- package/dist/canvasTiltShiftEffect.js +67 -0
- package/dist/canvasTiltShiftEffect.js.map +1 -0
- package/dist/canvasVignetteEffect.d.ts.map +1 -1
- package/dist/canvasVignetteEffect.js +36 -19
- package/dist/canvasVignetteEffect.js.map +1 -1
- package/dist/contract.d.ts +3 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +3 -0
- 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 -8
- package/src/canvasLensDistortionEffect.test.ts +149 -0
- package/src/canvasPosterizeEffect.test.ts +138 -0
- package/src/canvasTiltShiftEffect.test.ts +156 -0
|
@@ -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;
|
|
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
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
gradient.
|
|
30
|
-
gradient
|
|
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
|
-
|
|
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,
|
|
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":"AAOA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,
|
|
1
|
+
{"version":3,"file":"canvasVignetteEffect.js","sourceRoot":"","sources":["../src/canvasVignetteEffect.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.d.ts
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';
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -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';
|
package/dist/contract.js.map
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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.
|
|
42
|
-
"@flighthq/adjustments": "0.4.0-next.
|
|
43
|
-
"@flighthq/scene2d-canvas": "0.4.0-next.
|
|
44
|
-
"@flighthq/effects": "0.4.0-next.
|
|
45
|
-
"@flighthq/geometry": "0.4.0-next.
|
|
46
|
-
"@flighthq/registry": "0.4.0-next.
|
|
47
|
-
"@flighthq/types": "0.4.0-next.
|
|
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
|
+
});
|