@hyperframes/engine 0.8.44 → 0.8.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Sub-frame multi-sample motion blur (issue #4010).
3
+ *
4
+ * For each output frame the capture path seeks the timeline to K sub-frame times spread
5
+ * across the shutter window, captures each, and averages them. Unlike the catalog
6
+ * component, which stacks offset copies of an element and can only integrate
7
+ * translation, re-rendering at each sample integrates whatever the composition actually
8
+ * does inside the window: rotation, scale, opacity, filters, child animation.
9
+ *
10
+ * Shutter window, the After Effects model, in frames rather than seconds so the sample
11
+ * offsets are frame-rate independent:
12
+ *
13
+ * shutterTime = shutterAngle / 360 / fps
14
+ * windowStart = t + shutterPhase / 360 / fps
15
+ * sample k at windowStart + (k + 0.5) / N * shutterTime
16
+ */
17
+ /** Caller-facing options. Presence of the object is the opt-in; there is no enabled flag. */
18
+ export interface MotionBlurOptions {
19
+ /** Sub-frame captures averaged into one output frame. Default 16, clamped to 1..64. */
20
+ samplesPerFrame?: number;
21
+ /** Shutter window width in degrees of one frame. Default 180, AE's default. */
22
+ shutterAngle?: number;
23
+ /** Window offset in degrees. Default -90, which centres the window on the frame time. */
24
+ shutterPhase?: number;
25
+ /**
26
+ * Working space for the average. Default "srgb", matching After Effects' non-linearised
27
+ * 8-bpc working space and the catalog component. "linear" is physically correct light
28
+ * integration and diverges from both on high-contrast edges.
29
+ */
30
+ blend?: MotionBlurBlendSpace;
31
+ }
32
+ export type MotionBlurBlendSpace = "srgb" | "linear";
33
+ /** A resolved plan. The window is fixed by shutter angle/phase alone, independent of
34
+ * how many samples fill it, which is what lets the count vary per frame. */
35
+ export interface MotionBlurPlan {
36
+ readonly blend: MotionBlurBlendSpace;
37
+ /**
38
+ * Sub-frame ticks per output frame. The seek grid is `fps * subFrameDivisions`, which
39
+ * contains the output frame grid, so the frame-time instant never moves.
40
+ */
41
+ readonly subFrameDivisions: number;
42
+ /** Window start, in frames relative to the frame instant (`shutterPhase / 360`). */
43
+ readonly windowStartFrames: number;
44
+ /** Window width, in frames (`shutterAngle / 360`). */
45
+ readonly shutterFrames: number;
46
+ /** Explicit count from the caller (clamped to 1..64), or null for adaptive per-frame. */
47
+ readonly fixedSamplesPerFrame: number | null;
48
+ }
49
+ export declare const MAX_SAMPLES_PER_FRAME = 64;
50
+ /** Sample count for a frame confirmed to need no measurement (the adaptive floor, and
51
+ * the count an explicit `samplesPerFrame` defaults to). */
52
+ export declare const DEFAULT_SAMPLES_PER_FRAME = 16;
53
+ /** Resolve caller options into a plan, or null when motion blur is off. */
54
+ export declare function resolveMotionBlurPlan(options: MotionBlurOptions | undefined): MotionBlurPlan | null;
55
+ /**
56
+ * Absolute seek times for one output frame, in ascending order, for `samplesPerFrame`
57
+ * samples spread across the plan's window (independent of `plan.fixedSamplesPerFrame` —
58
+ * the caller decides how many samples this particular frame gets).
59
+ *
60
+ * Built from integer ticks rather than by adding floats to `frameIndex / fps`, so the
61
+ * page-side quantizer recovers the intended tick exactly on every host. Times before the
62
+ * composition start clamp to 0, which is what After Effects does at the first frame.
63
+ */
64
+ export declare function motionBlurSampleTimes(plan: MotionBlurPlan, frameIndex: number, fps: number, samplesPerFrame: number): number[];
65
+ /** The two true shutter-window edges (not samples — see `motionBlurProbeTimes`'s
66
+ * caller), for measuring motion magnitude before a sample count is chosen. */
67
+ export declare function motionBlurProbeTimes(plan: MotionBlurPlan, frameIndex: number, fps: number): {
68
+ windowStart: number;
69
+ windowEnd: number;
70
+ };
71
+ /**
72
+ * Is every frame the shutter window reads from known static, so this frame can reuse its
73
+ * predecessor's buffer instead of paying K captures?
74
+ *
75
+ * The static-frame dedup asks whether a frame is byte-identical to the one before it. With
76
+ * blur on that is not enough: the window reads content from either side of the frame
77
+ * instant, so a still frame next to a moving one still has to be captured. At a shutter
78
+ * angle above 360 the window spans whole neighbouring frames and the reuse would drop a
79
+ * frame of motion outright.
80
+ *
81
+ * The range covers the previous frame's window too, because reuse claims this frame's
82
+ * blurred output equals that one's. Contract taken from #4013 by Dante-dan, which carried
83
+ * this guard before we did.
84
+ */
85
+ export declare function motionBlurWindowIsStatic(plan: MotionBlurPlan, frameIndex: number, staticFrames: ReadonlySet<number>): boolean;
86
+ /** Diff magnitude → sample count, ascending and exhaustive. Calibration provenance and
87
+ * measured numbers are in the PR description for #4029; the 32-sample step is
88
+ * interpolated, not independently measured. */
89
+ export declare const ADAPTIVE_SAMPLE_STEPS: ReadonlyArray<{
90
+ readonly maxDiff: number;
91
+ readonly samples: number;
92
+ }>;
93
+ /** Sample count for a frame whose probe diff magnitude is `diff` (see `ADAPTIVE_SAMPLE_STEPS`). */
94
+ export declare function adaptiveSampleCount(diff: number): number;
95
+ /** GSAP property names that move an element in screen space. Matches the Transform
96
+ * categories of SUPPORTED_PROPS in packages/parsers/src/gsapConstants.ts, minus
97
+ * transformOrigin (that file's own classifyTweenPropertyGroup excludes it: a pivot-point
98
+ * modifier, not independent motion). Passed into computeStaticFrameSet's page.evaluate as data. */
99
+ export declare const SPATIAL_TWEEN_PROPERTIES: readonly string[];
100
+ /** Mean absolute byte difference between two probe captures (never a sample that
101
+ * enters the average) — the motion signal `adaptiveSampleCount` maps to a count. */
102
+ export declare function probeDiffMagnitude(a: Buffer, b: Buffer): number;
103
+ /**
104
+ * Running average of the sub-frame captures for one output frame.
105
+ *
106
+ * Samples are folded in as they arrive rather than held until the end, so peak memory is
107
+ * one accumulator plus one decoded sample regardless of K. Holding all K decoded frames
108
+ * would be (K + 1) * 8.3 MB at 1080p, which is 141 MB at the default 16 samples and
109
+ * 540 MB at the 64 maximum, thrown away and rebuilt on every frame of the render.
110
+ *
111
+ * Colour is summed premultiplied by alpha and un-premultiplied at the end, so a sample
112
+ * that is transparent at a pixel contributes no colour there. On opaque frames, the
113
+ * common case, that reduces to a plain mean.
114
+ */
115
+ export declare class MotionBlurAccumulator {
116
+ private readonly toWorking;
117
+ private readonly fromWorking;
118
+ /** Premultiplied r, g, b and alpha sums per pixel. Null until the first sample sets the size. */
119
+ private sums;
120
+ private width;
121
+ private height;
122
+ private count;
123
+ constructor(blend: MotionBlurBlendSpace);
124
+ /** Fold one captured sample PNG into the running average. */
125
+ add(png: Buffer): void;
126
+ /** Un-premultiply and encode the average. Throws if no sample was ever added. */
127
+ finish(): {
128
+ width: number;
129
+ height: number;
130
+ data: Uint8Array;
131
+ };
132
+ }
133
+ //# sourceMappingURL=motionBlur.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motionBlur.d.ts","sourceRoot":"","sources":["../../src/services/motionBlur.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErD;4EAC4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,oFAAoF;IACpF,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;2DAC2D;AAC3D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAc5C,2EAA2E;AAC3E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACrC,cAAc,GAAG,IAAI,CAqBvB;AAcD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,GACtB,MAAM,EAAE,CAMV;AAED;8EAC8E;AAC9E,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAWA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,OAAO,CAgBT;AAED;;+CAE+C;AAC/C,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAIA,CAAC;AAEF,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED;;;mGAGmG;AACnG,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EA8BrD,CAAC;AAEF;oFACoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAa/D;AA6BD;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IACxD,iGAAiG;IACjG,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,KAAK,CAAK;gBAEN,KAAK,EAAE,oBAAoB;IAKvC,6DAA6D;IAC7D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IA0BtB,iFAAiF;IACjF,MAAM,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE;CAgB9D"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Sub-frame multi-sample motion blur (issue #4010).
3
+ *
4
+ * For each output frame the capture path seeks the timeline to K sub-frame times spread
5
+ * across the shutter window, captures each, and averages them. Unlike the catalog
6
+ * component, which stacks offset copies of an element and can only integrate
7
+ * translation, re-rendering at each sample integrates whatever the composition actually
8
+ * does inside the window: rotation, scale, opacity, filters, child animation.
9
+ *
10
+ * Shutter window, the After Effects model, in frames rather than seconds so the sample
11
+ * offsets are frame-rate independent:
12
+ *
13
+ * shutterTime = shutterAngle / 360 / fps
14
+ * windowStart = t + shutterPhase / 360 / fps
15
+ * sample k at windowStart + (k + 0.5) / N * shutterTime
16
+ */
17
+ import { decodePng, srgbByteToLinear } from "../utils/alphaBlit.js";
18
+ export const MAX_SAMPLES_PER_FRAME = 64;
19
+ /** Sample count for a frame confirmed to need no measurement (the adaptive floor, and
20
+ * the count an explicit `samplesPerFrame` defaults to). */
21
+ export const DEFAULT_SAMPLES_PER_FRAME = 16;
22
+ const DEFAULT_SHUTTER_ANGLE = 180;
23
+ const DEFAULT_SHUTTER_PHASE = -90;
24
+ /**
25
+ * Ticks per output frame on the seek grid. Fine enough that the rounding error on a
26
+ * sample time is under 1/8192 of a frame, coarse enough to stay exactly representable.
27
+ */
28
+ const SUB_FRAME_DIVISIONS = 4096;
29
+ function finiteOr(value, fallback) {
30
+ return Number.isFinite(value) ? value : fallback;
31
+ }
32
+ /** Resolve caller options into a plan, or null when motion blur is off. */
33
+ export function resolveMotionBlurPlan(options) {
34
+ if (!options)
35
+ return null;
36
+ const fixedSamplesPerFrame = options.samplesPerFrame === undefined
37
+ ? null
38
+ : Math.min(MAX_SAMPLES_PER_FRAME, Math.max(1, Math.round(finiteOr(options.samplesPerFrame, DEFAULT_SAMPLES_PER_FRAME))));
39
+ const shutterAngle = finiteOr(options.shutterAngle, DEFAULT_SHUTTER_ANGLE);
40
+ const shutterPhase = finiteOr(options.shutterPhase, DEFAULT_SHUTTER_PHASE);
41
+ const blend = options.blend === "linear" ? "linear" : "srgb";
42
+ return {
43
+ blend,
44
+ subFrameDivisions: SUB_FRAME_DIVISIONS,
45
+ windowStartFrames: shutterPhase / 360,
46
+ shutterFrames: shutterAngle / 360,
47
+ fixedSamplesPerFrame,
48
+ };
49
+ }
50
+ /** Tick offsets from the frame instant for `samplesPerFrame` samples spread evenly
51
+ * across the plan's window, ascending. Integers, so every host agrees. */
52
+ function sampleTickOffsets(plan, samplesPerFrame) {
53
+ const offsets = [];
54
+ for (let k = 0; k < samplesPerFrame; k++) {
55
+ const offsetFrames = plan.windowStartFrames + ((k + 0.5) / samplesPerFrame) * plan.shutterFrames;
56
+ offsets.push(Math.round(offsetFrames * plan.subFrameDivisions));
57
+ }
58
+ return offsets;
59
+ }
60
+ /**
61
+ * Absolute seek times for one output frame, in ascending order, for `samplesPerFrame`
62
+ * samples spread across the plan's window (independent of `plan.fixedSamplesPerFrame` —
63
+ * the caller decides how many samples this particular frame gets).
64
+ *
65
+ * Built from integer ticks rather than by adding floats to `frameIndex / fps`, so the
66
+ * page-side quantizer recovers the intended tick exactly on every host. Times before the
67
+ * composition start clamp to 0, which is what After Effects does at the first frame.
68
+ */
69
+ export function motionBlurSampleTimes(plan, frameIndex, fps, samplesPerFrame) {
70
+ const grid = fps * plan.subFrameDivisions;
71
+ const frameTicks = frameIndex * plan.subFrameDivisions;
72
+ return sampleTickOffsets(plan, samplesPerFrame).map((offset) => Math.max(0, frameTicks + offset) / grid);
73
+ }
74
+ /** The two true shutter-window edges (not samples — see `motionBlurProbeTimes`'s
75
+ * caller), for measuring motion magnitude before a sample count is chosen. */
76
+ export function motionBlurProbeTimes(plan, frameIndex, fps) {
77
+ const grid = fps * plan.subFrameDivisions;
78
+ const frameTicks = frameIndex * plan.subFrameDivisions;
79
+ const startTick = Math.round(plan.windowStartFrames * plan.subFrameDivisions);
80
+ const endTick = Math.round((plan.windowStartFrames + plan.shutterFrames) * plan.subFrameDivisions);
81
+ return {
82
+ windowStart: Math.max(0, frameTicks + startTick) / grid,
83
+ windowEnd: Math.max(0, frameTicks + endTick) / grid,
84
+ };
85
+ }
86
+ /**
87
+ * Is every frame the shutter window reads from known static, so this frame can reuse its
88
+ * predecessor's buffer instead of paying K captures?
89
+ *
90
+ * The static-frame dedup asks whether a frame is byte-identical to the one before it. With
91
+ * blur on that is not enough: the window reads content from either side of the frame
92
+ * instant, so a still frame next to a moving one still has to be captured. At a shutter
93
+ * angle above 360 the window spans whole neighbouring frames and the reuse would drop a
94
+ * frame of motion outright.
95
+ *
96
+ * The range covers the previous frame's window too, because reuse claims this frame's
97
+ * blurred output equals that one's. Contract taken from #4013 by Dante-dan, which carried
98
+ * this guard before we did.
99
+ */
100
+ export function motionBlurWindowIsStatic(plan, frameIndex, staticFrames) {
101
+ // Bound by the outermost sample this frame could take (its half-step inset from the
102
+ // true edge), not the edge itself. An adaptive plan's K is unknown here, so use the
103
+ // widest possible inset (K=64) rather than the true edge, which any chosen K stays inside.
104
+ const worstCaseSamples = plan.fixedSamplesPerFrame ?? MAX_SAMPLES_PER_FRAME;
105
+ const inset = plan.shutterFrames / (2 * worstCaseSamples);
106
+ const minOffsetFrames = plan.windowStartFrames + inset;
107
+ const maxOffsetFrames = plan.windowStartFrames + plan.shutterFrames - inset;
108
+ const first = Math.floor(frameIndex - 1 + Math.min(0, minOffsetFrames));
109
+ // A sample landing inside [F, F+1) reads content the frame set only pins down at both
110
+ // ends, so the frame after the last one touched has to be static as well.
111
+ const last = Math.floor(frameIndex + Math.max(0, maxOffsetFrames)) + 1;
112
+ for (let frame = first; frame <= last; frame++) {
113
+ if (!staticFrames.has(frame))
114
+ return false;
115
+ }
116
+ return true;
117
+ }
118
+ /** Diff magnitude → sample count, ascending and exhaustive. Calibration provenance and
119
+ * measured numbers are in the PR description for #4029; the 32-sample step is
120
+ * interpolated, not independently measured. */
121
+ export const ADAPTIVE_SAMPLE_STEPS = [
122
+ { maxDiff: 3, samples: DEFAULT_SAMPLES_PER_FRAME },
123
+ { maxDiff: 6, samples: 32 },
124
+ { maxDiff: Infinity, samples: MAX_SAMPLES_PER_FRAME },
125
+ ];
126
+ /** Sample count for a frame whose probe diff magnitude is `diff` (see `ADAPTIVE_SAMPLE_STEPS`). */
127
+ export function adaptiveSampleCount(diff) {
128
+ for (const step of ADAPTIVE_SAMPLE_STEPS) {
129
+ if (diff <= step.maxDiff)
130
+ return step.samples;
131
+ }
132
+ return MAX_SAMPLES_PER_FRAME;
133
+ }
134
+ /** GSAP property names that move an element in screen space. Matches the Transform
135
+ * categories of SUPPORTED_PROPS in packages/parsers/src/gsapConstants.ts, minus
136
+ * transformOrigin (that file's own classifyTweenPropertyGroup excludes it: a pivot-point
137
+ * modifier, not independent motion). Passed into computeStaticFrameSet's page.evaluate as data. */
138
+ export const SPATIAL_TWEEN_PROPERTIES = [
139
+ "x",
140
+ "y",
141
+ "z",
142
+ "xPercent",
143
+ "yPercent",
144
+ "rotation",
145
+ "rotate",
146
+ "rotationX",
147
+ "rotationY",
148
+ "rotationZ",
149
+ "scale",
150
+ "scaleX",
151
+ "scaleY",
152
+ "scaleZ",
153
+ "skewX",
154
+ "skewY",
155
+ "perspective",
156
+ "transformPerspective",
157
+ "top",
158
+ "left",
159
+ "right",
160
+ "bottom",
161
+ "width",
162
+ "height",
163
+ "translate",
164
+ "translateX",
165
+ "translateY",
166
+ "translateZ",
167
+ "transform",
168
+ ];
169
+ /** Mean absolute byte difference between two probe captures (never a sample that
170
+ * enters the average) — the motion signal `adaptiveSampleCount` maps to a count. */
171
+ export function probeDiffMagnitude(a, b) {
172
+ const decodedA = decodePng(a);
173
+ const decodedB = decodePng(b);
174
+ if (decodedA.width !== decodedB.width || decodedA.height !== decodedB.height) {
175
+ throw new Error(`probeDiffMagnitude: sample geometry mismatch (${decodedA.width}x${decodedA.height} vs ${decodedB.width}x${decodedB.height})`);
176
+ }
177
+ let sum = 0;
178
+ for (let i = 0; i < decodedA.data.length; i++) {
179
+ sum += Math.abs(decodedA.data[i] - decodedB.data[i]);
180
+ }
181
+ return sum / decodedA.data.length;
182
+ }
183
+ const SRGB_TO_LINEAR = (() => {
184
+ const lut = new Float64Array(256);
185
+ for (let i = 0; i < 256; i++)
186
+ lut[i] = srgbByteToLinear(i);
187
+ return lut;
188
+ })();
189
+ /**
190
+ * Identity table for the sRGB working space, so the accumulate loop is one shape for both
191
+ * spaces instead of two near-copies. Measured on a 1920x1080 frame at 16 samples, reading
192
+ * this table costs nothing against using the byte directly: the two variants time within
193
+ * each other's run-to-run noise.
194
+ */
195
+ const SRGB_PASSTHROUGH = (() => {
196
+ const lut = new Float64Array(256);
197
+ for (let i = 0; i < 256; i++)
198
+ lut[i] = i;
199
+ return lut;
200
+ })();
201
+ function linearToByte(value) {
202
+ const c = value <= 0.0031308 ? value * 12.92 : 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
203
+ return clampByte(c * 255);
204
+ }
205
+ function clampByte(value) {
206
+ return Math.min(255, Math.max(0, Math.round(value)));
207
+ }
208
+ /**
209
+ * Running average of the sub-frame captures for one output frame.
210
+ *
211
+ * Samples are folded in as they arrive rather than held until the end, so peak memory is
212
+ * one accumulator plus one decoded sample regardless of K. Holding all K decoded frames
213
+ * would be (K + 1) * 8.3 MB at 1080p, which is 141 MB at the default 16 samples and
214
+ * 540 MB at the 64 maximum, thrown away and rebuilt on every frame of the render.
215
+ *
216
+ * Colour is summed premultiplied by alpha and un-premultiplied at the end, so a sample
217
+ * that is transparent at a pixel contributes no colour there. On opaque frames, the
218
+ * common case, that reduces to a plain mean.
219
+ */
220
+ export class MotionBlurAccumulator {
221
+ toWorking;
222
+ fromWorking;
223
+ /** Premultiplied r, g, b and alpha sums per pixel. Null until the first sample sets the size. */
224
+ sums = null;
225
+ width = 0;
226
+ height = 0;
227
+ count = 0;
228
+ constructor(blend) {
229
+ this.toWorking = blend === "linear" ? SRGB_TO_LINEAR : SRGB_PASSTHROUGH;
230
+ this.fromWorking = blend === "linear" ? linearToByte : clampByte;
231
+ }
232
+ /** Fold one captured sample PNG into the running average. */
233
+ add(png) {
234
+ const { width, height, data } = decodePng(png);
235
+ if (!this.sums) {
236
+ this.width = width;
237
+ this.height = height;
238
+ // float32 holds a 64-sample premultiplied sum (max 16320) far more precisely than
239
+ // the 1/255 quantum the result is rounded to.
240
+ this.sums = new Float32Array(width * height * 4);
241
+ }
242
+ else if (width !== this.width || height !== this.height) {
243
+ throw new Error(`MotionBlurAccumulator: sample geometry mismatch (${width}x${height} vs ${this.width}x${this.height})`);
244
+ }
245
+ const sums = this.sums;
246
+ const toWorking = this.toWorking;
247
+ for (let i = 0; i < sums.length; i += 4) {
248
+ const alpha = data[i + 3] / 255;
249
+ sums[i] = sums[i] + toWorking[data[i]] * alpha;
250
+ sums[i + 1] = sums[i + 1] + toWorking[data[i + 1]] * alpha;
251
+ sums[i + 2] = sums[i + 2] + toWorking[data[i + 2]] * alpha;
252
+ sums[i + 3] = sums[i + 3] + alpha;
253
+ }
254
+ this.count += 1;
255
+ }
256
+ /** Un-premultiply and encode the average. Throws if no sample was ever added. */
257
+ finish() {
258
+ const sums = this.sums;
259
+ if (!sums)
260
+ throw new Error("MotionBlurAccumulator: no samples");
261
+ const out = new Uint8Array(sums.length);
262
+ for (let i = 0; i < sums.length; i += 4) {
263
+ const alphaSum = sums[i + 3];
264
+ out[i + 3] = clampByte((alphaSum / this.count) * 255);
265
+ if (alphaSum === 0)
266
+ continue;
267
+ const scale = 1 / alphaSum;
268
+ out[i] = this.fromWorking(sums[i] * scale);
269
+ out[i + 1] = this.fromWorking(sums[i + 1] * scale);
270
+ out[i + 2] = this.fromWorking(sums[i + 2] * scale);
271
+ }
272
+ return { width: this.width, height: this.height, data: out };
273
+ }
274
+ }
275
+ //# sourceMappingURL=motionBlur.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motionBlur.js","sourceRoot":"","sources":["../../src/services/motionBlur.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAqCpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC;2DAC2D;AAC3D,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAC5C,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,qBAAqB,GAAG,CAAC,EAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC,SAAS,QAAQ,CAAC,KAAyB,EAAE,QAAgB;IAC3D,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CACnC,OAAsC;IAEtC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,oBAAoB,GACxB,OAAO,CAAC,eAAe,KAAK,SAAS;QACnC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,GAAG,CACN,qBAAqB,EACrB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC,CAAC,CACtF,CAAC;IACR,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAyB,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAEnF,OAAO;QACL,KAAK;QACL,iBAAiB,EAAE,mBAAmB;QACtC,iBAAiB,EAAE,YAAY,GAAG,GAAG;QACrC,aAAa,EAAE,YAAY,GAAG,GAAG;QACjC,oBAAoB;KACrB,CAAC;AACJ,CAAC;AAED;0EAC0E;AAC1E,SAAS,iBAAiB,CAAC,IAAoB,EAAE,eAAuB;IACtE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,YAAY,GAChB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,eAAe,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAoB,EACpB,UAAkB,EAClB,GAAW,EACX,eAAuB;IAEvB,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,iBAAiB,CAAC;IAC1C,MAAM,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC;IACvD,OAAO,iBAAiB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,GAAG,CACjD,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CACpD,CAAC;AACJ,CAAC;AAED;8EAC8E;AAC9E,MAAM,UAAU,oBAAoB,CAClC,IAAoB,EACpB,UAAkB,EAClB,GAAW;IAKX,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,iBAAiB,CAAC;IAC1C,MAAM,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,CAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,iBAAiB,CACvE,CAAC;IACF,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,IAAI;QACvD,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,IAAI;KACpD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAoB,EACpB,UAAkB,EAClB,YAAiC;IAEjC,oFAAoF;IACpF,oFAAoF;IACpF,2FAA2F;IAC3F,MAAM,gBAAgB,GAAG,IAAI,CAAC,oBAAoB,IAAI,qBAAqB,CAAC;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;IACvD,MAAM,eAAe,GAAG,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;IACxE,sFAAsF;IACtF,0EAA0E;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC;IACvE,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;+CAE+C;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAG7B;IACH,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE;IAClD,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3B,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,EAAE;CACtD,CAAC;AAEF,mGAAmG;AACnG,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,KAAK,MAAM,IAAI,IAAI,qBAAqB,EAAE,CAAC;QACzC,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IAChD,CAAC;IACD,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAED;;;mGAGmG;AACnG,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,GAAG;IACH,GAAG;IACH,GAAG;IACH,UAAU;IACV,UAAU;IACV,UAAU;IACV,QAAQ;IACR,WAAW;IACX,WAAW;IACX,WAAW;IACX,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,OAAO;IACP,aAAa;IACb,sBAAsB;IACtB,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,WAAW;CACZ,CAAC;AAEF;oFACoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,CAAS,EAAE,CAAS;IACrD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CACb,iDAAiD,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,OAAO,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,GAAG,CAC9H,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,GAAG,IAAI,IAAI,CAAC,GAAG,CAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAY,GAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAY,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;AACpC,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE;IAC3B,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC;AACb,CAAC,CAAC,EAAE,CAAC;AAEL;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,GAAG,CAAC;AACb,CAAC,CAAC,EAAE,CAAC;AAEL,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,CAAC,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;IACxF,OAAO,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,qBAAqB;IACf,SAAS,CAAe;IACxB,WAAW,CAA4B;IACxD,iGAAiG;IACzF,IAAI,GAAwB,IAAI,CAAC;IACjC,KAAK,GAAG,CAAC,CAAC;IACV,MAAM,GAAG,CAAC,CAAC;IACX,KAAK,GAAG,CAAC,CAAC;IAElB,YAAY,KAA2B;QACrC,IAAI,CAAC,SAAS,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC;QACxE,IAAI,CAAC,WAAW,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,CAAC;IAED,6DAA6D;IAC7D,GAAG,CAAC,GAAW;QACb,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,kFAAkF;YAClF,8CAA8C;YAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,YAAY,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,oDAAoD,KAAK,IAAI,MAAM,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CACvG,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,GAAG,CAAC;YAC5C,IAAI,CAAC,CAAC,CAAC,GAAI,IAAI,CAAC,CAAC,CAAY,GAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAW,CAAY,GAAG,KAAK,CAAC;YACjF,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAI,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAW,CAAY,GAAG,KAAK,CAAC;YAC7F,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAI,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAW,CAAY,GAAG,KAAK,CAAC;YAC7F,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,KAAK,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,iFAAiF;IACjF,MAAM;QACJ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAEhE,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAW,CAAC;YACvC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;YACtD,IAAI,QAAQ,KAAK,CAAC;gBAAE,SAAS;YAC7B,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC;YAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAE,IAAI,CAAC,CAAC,CAAY,GAAG,KAAK,CAAC,CAAC;YACvD,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,KAAK,CAAC,CAAC;YAC/D,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,KAAK,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC/D,CAAC;CACF"}
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * as video must expose `window.__hf` implementing the HfProtocol interface.
6
6
  */
7
7
  import type { Fps } from "@hyperframes/core";
8
+ import type { MotionBlurOptions } from "./services/motionBlur.js";
8
9
  /**
9
10
  * Outcome of waiting for a sub-composition's GSAP timelines to register.
10
11
  * Threaded string-typed through `CapturePerfSummary` / `RenderPerfSummary` /
@@ -82,11 +83,22 @@ export interface HfTransitionMeta {
82
83
  * GSAP, Framer Motion, CSS animations, Three.js — anything works as long
83
84
  * as `seek()` produces deterministic visual output for a given time.
84
85
  */
86
+ /**
87
+ * Per-seek controls the page honours. Both default off, which is an ordinary frame seek.
88
+ *
89
+ * `suppressEvents` stops a composition's own timeline callbacks from firing, and
90
+ * `subFrameDivisions` refines the grid the page quantizes onto so a fractional time is
91
+ * not floored back onto the output frame. Motion-blur sampling sets both.
92
+ */
93
+ export interface HfSeekOptions {
94
+ suppressEvents?: boolean;
95
+ subFrameDivisions?: number;
96
+ }
85
97
  export interface HfProtocol {
86
98
  /** Total duration of the composition in seconds */
87
99
  duration: number;
88
100
  /** Seek to a specific time. Must produce deterministic visual output. */
89
- seek(time: number): void;
101
+ seek(time: number, options?: HfSeekOptions): void;
90
102
  /** Optional: media elements the engine should handle */
91
103
  media?: HfMediaElement[];
92
104
  /** Optional: shader transition metadata, populated by @hyperframes/shader-transitions */
@@ -113,6 +125,12 @@ export interface CaptureOptions {
113
125
  fps: Fps;
114
126
  format?: "jpeg" | "png";
115
127
  quality?: number;
128
+ /**
129
+ * Opt into sub-frame multi-sample motion blur (issue #4010). Absent means off and the
130
+ * capture path is byte-identical to a render without it. Requires `format: "png"` and
131
+ * screenshot capture mode; see `services/motionBlur.ts`.
132
+ */
133
+ motionBlur?: MotionBlurOptions;
116
134
  deviceScaleFactor?: number;
117
135
  /**
118
136
  * Opt into Chrome's capture-beyond-viewport screenshot path. Leave undefined
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,13 @@ export declare function decodePng(buf: Buffer): {
17
17
  height: number;
18
18
  data: Uint8Array;
19
19
  };
20
+ /**
21
+ * Encode 8-bit RGBA pixels as a PNG, the inverse of `decodePng`.
22
+ *
23
+ * Byte-identical for identical pixels: every scanline takes filter 0 and zlib runs at a
24
+ * fixed level, so a render that samples the same instant twice produces the same file.
25
+ */
26
+ export declare function encodePng(width: number, height: number, rgba: Uint8Array): Buffer;
20
27
  /**
21
28
  * Decode a 16-bit RGB PNG (from FFmpeg) to an rgb48le Buffer.
22
29
  *
@@ -31,6 +38,32 @@ export declare function decodePngToRgb48le(buf: Buffer): {
31
38
  height: number;
32
39
  data: Buffer;
33
40
  };
41
+ /**
42
+ * Build a 256-entry LUT: sRGB 8-bit value → HDR 16-bit signal value.
43
+ *
44
+ * Pipeline per channel: sRGB EOTF (decode gamma) → linear → HDR OETF → 16-bit.
45
+ *
46
+ * ## Convention
47
+ *
48
+ * "Linear" here means **scene light in [0, 1] relative to SDR reference white**
49
+ * (not absolute nits). The HLG branch applies the OETF directly — no OOTF (no
50
+ * gamma 1.2 scene→display conversion). This is the right choice for DOM
51
+ * overlays that will be composited ON TOP of HLG video pixels (which are
52
+ * already in HLG signal space); we need the overlay to sit in the same space
53
+ * as what it’s blending onto. Applying the OOTF here would double-apply it
54
+ * when the HDR video already carries scene-light semantics.
55
+ *
56
+ * For PQ, SDR white is placed at 203 nits per ITU-R BT.2408 ("SDR white"
57
+ * reference level) and normalized against 10,000-nit peak. This lets SDR
58
+ * content (text, UI) sit at the conventional SDR-white brightness within a
59
+ * PQ frame rather than at peak brightness.
60
+ *
61
+ * Note: converts the transfer function but not the color primaries (bt709 →
62
+ * bt2020). For neutral/near-neutral content (text, UI) the gamut difference
63
+ * is negligible.
64
+ */
65
+ /** sRGB EOTF: an 8-bit signal value to linear light, 0 to 1 relative to SDR white. */
66
+ export declare function srgbByteToLinear(value: number): number;
34
67
  /**
35
68
  * Alpha-composite a DOM RGBA overlay (8-bit sRGB) onto an HDR canvas
36
69
  * (rgb48le) in-place.
@@ -1 +1 @@
1
- {"version":3,"file":"alphaBlit.d.ts","sourceRoot":"","sources":["../../src/utils/alphaBlit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiJH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAuB1F;AAID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA6B/F;AAqFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,KAAK,GAAG,IAAI,GAAG,MAAc,GACtC,IAAI,CAiCN;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACtC,MAAM,CAOR;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC9C,IAAI,CAiFN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC9C,IAAI,CA0HN;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;AA4F7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,SAAkB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CA+ER;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAavE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAwCjE"}
1
+ {"version":3,"file":"alphaBlit.d.ts","sourceRoot":"","sources":["../../src/utils/alphaBlit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkJH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAuB1F;AAoBD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAwBjF;AAID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA6B/F;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGtD;AAyDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,KAAK,GAAG,IAAI,GAAG,MAAc,GACtC,IAAI,CAiCN;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACtC,MAAM,CAOR;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC9C,IAAI,CAiFN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAC9C,IAAI,CA0HN;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;AA4F7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,SAAkB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CA+ER;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAavE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAwCjE"}
@@ -4,7 +4,8 @@
4
4
  * Replaces per-frame FFmpeg spawns for the two-pass HDR compositing path.
5
5
  * Uses only Node.js built-ins (zlib) — no additional dependencies.
6
6
  */
7
- import { inflateSync } from "zlib";
7
+ import { deflateSync, inflateSync } from "zlib";
8
+ import { chunkCrc32 } from "./crc32.js";
8
9
  // ── PNG decoder ───────────────────────────────────────────────────────────────
9
10
  function paeth(a, b, c) {
10
11
  const p = a + b - c;
@@ -156,6 +157,50 @@ export function decodePng(buf) {
156
157
  }
157
158
  return { width, height, data: output };
158
159
  }
160
+ // ── PNG encoder ──────────────────────────────────────────────────
161
+ /**
162
+ * Intermediate frames are read once by the encoder and deleted, so encode speed matters
163
+ * and compression ratio does not. Measured on a 1920x1080 frame: level 1 takes 214 ms for
164
+ * 4.14 MiB against level 6's 293 ms for 3.98 MiB.
165
+ */
166
+ const PNG_DEFLATE_LEVEL = 1;
167
+ function pngChunk(type, body) {
168
+ const header = Buffer.alloc(8);
169
+ header.writeUInt32BE(body.length, 0);
170
+ header.write(type, 4, "ascii");
171
+ const trailer = Buffer.alloc(4);
172
+ trailer.writeUInt32BE(chunkCrc32(type, body), 0);
173
+ return Buffer.concat([header, body, trailer]);
174
+ }
175
+ /**
176
+ * Encode 8-bit RGBA pixels as a PNG, the inverse of `decodePng`.
177
+ *
178
+ * Byte-identical for identical pixels: every scanline takes filter 0 and zlib runs at a
179
+ * fixed level, so a render that samples the same instant twice produces the same file.
180
+ */
181
+ export function encodePng(width, height, rgba) {
182
+ const expected = width * height * 4;
183
+ if (rgba.length !== expected) {
184
+ throw new Error(`encodePng: expected ${expected} bytes for ${width}x${height}, got ${rgba.length}`);
185
+ }
186
+ const stride = width * 4;
187
+ const filtered = Buffer.alloc((stride + 1) * height);
188
+ for (let y = 0; y < height; y++) {
189
+ filtered[y * (stride + 1)] = 0;
190
+ filtered.set(rgba.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1);
191
+ }
192
+ const ihdr = Buffer.alloc(13);
193
+ ihdr.writeUInt32BE(width, 0);
194
+ ihdr.writeUInt32BE(height, 4);
195
+ ihdr[8] = 8; // bit depth
196
+ ihdr[9] = 6; // colour type RGBA
197
+ return Buffer.concat([
198
+ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
199
+ pngChunk("IHDR", ihdr),
200
+ pngChunk("IDAT", deflateSync(filtered, { level: PNG_DEFLATE_LEVEL })),
201
+ pngChunk("IEND", Buffer.alloc(0)),
202
+ ]);
203
+ }
159
204
  // ── 16-bit PNG decoder ────────────────────────────────────────────────────────
160
205
  /**
161
206
  * Decode a 16-bit RGB PNG (from FFmpeg) to an rgb48le Buffer.
@@ -216,6 +261,11 @@ export function decodePngToRgb48le(buf) {
216
261
  * bt2020). For neutral/near-neutral content (text, UI) the gamut difference
217
262
  * is negligible.
218
263
  */
264
+ /** sRGB EOTF: an 8-bit signal value to linear light, 0 to 1 relative to SDR white. */
265
+ export function srgbByteToLinear(value) {
266
+ const v = value / 255;
267
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
268
+ }
219
269
  function buildSrgbToSignalLut(transfer) {
220
270
  const lut = new Uint16Array(256);
221
271
  // HLG OETF constants (Rec. 2100)
@@ -235,9 +285,7 @@ function buildSrgbToSignalLut(transfer) {
235
285
  lut[i] = i * 257;
236
286
  continue;
237
287
  }
238
- // sRGB EOTF: signal → linear (range 0–1, relative to SDR white)
239
- const v = i / 255;
240
- const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
288
+ const linear = srgbByteToLinear(i);
241
289
  let signal;
242
290
  if (transfer === "hlg") {
243
291
  signal =