@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.
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/services/chunkEncoder.d.ts +33 -0
- package/dist/services/chunkEncoder.d.ts.map +1 -1
- package/dist/services/chunkEncoder.js +110 -1
- package/dist/services/chunkEncoder.js.map +1 -1
- package/dist/services/frameCapture.d.ts +39 -0
- package/dist/services/frameCapture.d.ts.map +1 -1
- package/dist/services/frameCapture.js +288 -99
- package/dist/services/frameCapture.js.map +1 -1
- package/dist/services/motionBlur.d.ts +133 -0
- package/dist/services/motionBlur.d.ts.map +1 -0
- package/dist/services/motionBlur.js +275 -0
- package/dist/services/motionBlur.js.map +1 -0
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/alphaBlit.d.ts +33 -0
- package/dist/utils/alphaBlit.d.ts.map +1 -1
- package/dist/utils/alphaBlit.js +52 -4
- package/dist/utils/alphaBlit.js.map +1 -1
- package/dist/utils/crc32.d.ts +2 -0
- package/dist/utils/crc32.d.ts.map +1 -0
- package/dist/utils/crc32.js +35 -0
- package/dist/utils/crc32.js.map +1 -0
- package/dist/utils/ffprobe.d.ts.map +1 -1
- package/dist/utils/ffprobe.js +1 -31
- package/dist/utils/ffprobe.js.map +1 -1
- package/package.json +3 -3
|
@@ -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":"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"}
|
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
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;
|
|
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;
|
|
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"}
|
package/dist/utils/alphaBlit.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|