pixi-effects 0.22.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +9 -7
- package/ai/SKILL.md +5 -3
- package/ai/chat-template.html +3 -3
- package/ai/reference/cheatsheet.md +8 -5
- package/ai/reference/pitfalls.md +13 -1
- package/ai/reference/recipes.md +131 -0
- package/ai/template.html +5 -5
- package/ai/tools/check.mjs +7 -0
- package/dist/{Base-gu-5X0Cv.d.ts → Base-BgnRXbtJ.d.ts} +1 -1
- package/dist/{Base-BzlPgd-D.d.cts → Base-DHVKqA46.d.cts} +1 -1
- package/dist/Composition-2GJSCHPP.cjs +16 -0
- package/dist/{Composition-BK5SH6J7.cjs.map → Composition-2GJSCHPP.cjs.map} +1 -1
- package/dist/Composition-C4FSWJZF.js +7 -0
- package/dist/{Composition-Q5TVG3HM.js.map → Composition-C4FSWJZF.js.map} +1 -1
- package/dist/Controller.cjs +2 -2
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/Controller.js +1 -1
- package/dist/{Movie-DMxOoTdW.d.cts → Movie-CTpN1wYL.d.cts} +3 -1
- package/dist/{Movie--_TJvdJa.d.ts → Movie-FM5m67V0.d.ts} +3 -1
- package/dist/Presenter.cjs +2 -2
- package/dist/Presenter.d.cts +2 -2
- package/dist/Presenter.d.ts +2 -2
- package/dist/Presenter.js +1 -1
- package/dist/{blendModes-HCP5XTNP.js → blendModes-G3NOAU3T.js} +5 -5
- package/dist/{blendModes-HCP5XTNP.js.map → blendModes-G3NOAU3T.js.map} +1 -1
- package/dist/{blendModes-5E4G2ZZQ.cjs → blendModes-YV43K6E7.cjs} +6 -6
- package/dist/{blendModes-5E4G2ZZQ.cjs.map → blendModes-YV43K6E7.cjs.map} +1 -1
- package/dist/{chunk-K4JVI5HK.cjs → chunk-3DLYAPK7.cjs} +7 -9
- package/dist/{chunk-K4JVI5HK.cjs.map → chunk-3DLYAPK7.cjs.map} +1 -1
- package/dist/{chunk-YEPDQ7I3.js → chunk-AXZUKNXF.js} +6 -6
- package/dist/{chunk-YEPDQ7I3.js.map → chunk-AXZUKNXF.js.map} +1 -1
- package/dist/{chunk-NHICCDTC.js → chunk-BXOCEZ4U.js} +12 -3
- package/dist/chunk-BXOCEZ4U.js.map +1 -0
- package/dist/{chunk-IFZOI3JQ.cjs → chunk-DP37TTMO.cjs} +842 -40
- package/dist/chunk-DP37TTMO.cjs.map +1 -0
- package/dist/{chunk-BPTW7L25.cjs → chunk-ECA2WNRX.cjs} +23 -9
- package/dist/chunk-ECA2WNRX.cjs.map +1 -0
- package/dist/{chunk-DC4JPY6A.js → chunk-PRZ4U6JN.js} +814 -22
- package/dist/chunk-PRZ4U6JN.js.map +1 -0
- package/dist/chunk-SY27HZC5.cjs +1727 -0
- package/dist/chunk-SY27HZC5.cjs.map +1 -0
- package/dist/{chunk-E7RONUZ7.js → chunk-TLYTYBWX.js} +20 -6
- package/dist/chunk-TLYTYBWX.js.map +1 -0
- package/dist/{chunk-UZTKEYFP.cjs → chunk-TWZNOUM7.cjs} +12 -3
- package/dist/chunk-TWZNOUM7.cjs.map +1 -0
- package/dist/chunk-XS6GSJCN.js +1724 -0
- package/dist/chunk-XS6GSJCN.js.map +1 -0
- package/dist/index.cjs +227 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +166 -16
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{music-FUN7N225.js → music-AF4VTFAF.js} +3 -3
- package/dist/{music-FUN7N225.js.map → music-AF4VTFAF.js.map} +1 -1
- package/dist/{music-GEAH2XCT.cjs → music-KB7YZLHP.cjs} +7 -7
- package/dist/{music-GEAH2XCT.cjs.map → music-KB7YZLHP.cjs.map} +1 -1
- package/dist/{sfx-2DYTYNYW.cjs → sfx-2O6UQEUP.cjs} +8 -8
- package/dist/{sfx-2DYTYNYW.cjs.map → sfx-2O6UQEUP.cjs.map} +1 -1
- package/dist/{sfx-3IDAJ3UZ.js → sfx-VUKARHDX.js} +3 -3
- package/dist/{sfx-3IDAJ3UZ.js.map → sfx-VUKARHDX.js.map} +1 -1
- package/dist/three.cjs +7 -7
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +3 -3
- package/dist/{types-DBbZVKGS.d.cts → types-CoT0ypcx.d.cts} +60 -4
- package/dist/{types-DBbZVKGS.d.ts → types-CoT0ypcx.d.ts} +60 -4
- package/docs/dsl.md +138 -5
- package/llms-full.txt +295 -14
- package/package.json +1 -1
- package/dist/Composition-BK5SH6J7.cjs +0 -16
- package/dist/Composition-Q5TVG3HM.js +0 -7
- package/dist/chunk-BPTW7L25.cjs.map +0 -1
- package/dist/chunk-DC4JPY6A.js.map +0 -1
- package/dist/chunk-E7RONUZ7.js.map +0 -1
- package/dist/chunk-IFZOI3JQ.cjs.map +0 -1
- package/dist/chunk-MHOO2CVU.cjs +0 -840
- package/dist/chunk-MHOO2CVU.cjs.map +0 -1
- package/dist/chunk-NHICCDTC.js.map +0 -1
- package/dist/chunk-UQDEDYVM.js +0 -838
- package/dist/chunk-UQDEDYVM.js.map +0 -1
- package/dist/chunk-UZTKEYFP.cjs.map +0 -1
|
@@ -196,9 +196,26 @@ interface DissolveTransition extends TransitionCommon {
|
|
|
196
196
|
/** 0..1 edge softness within each chunk. Default 0.05. */
|
|
197
197
|
smoothing?: number;
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
interface LumaTransition extends TransitionCommon {
|
|
200
|
+
kind: 'luma';
|
|
201
|
+
/** The brightness map the wipe follows: `'linear'` (left to right), `'diagonal'`, `'radial'` (from the middle), or the name of a grayscale image asset (dark parts change first). Default `'linear'`. */
|
|
202
|
+
map?: string;
|
|
203
|
+
/** Width of the soft band between the scenes, in brightness units (0..1). Default 0.1. */
|
|
204
|
+
softness?: number;
|
|
205
|
+
/** Reverse the map (light parts change first). */
|
|
206
|
+
flip?: boolean;
|
|
207
|
+
}
|
|
208
|
+
type TransitionSpec = CrossfadeTransition | WipeTransition | IrisTransition | SlideTransition | DipTransition | ZoomTransition | DissolveTransition | LumaTransition;
|
|
200
209
|
/** How a layer blends with what is behind it: the CSS `mix-blend-mode` names, plus `add` and `linear-burn`. */
|
|
201
210
|
type BlendModeName = 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' | 'hard-light' | 'color-dodge' | 'color-burn' | 'darken' | 'lighten' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'linear-burn';
|
|
211
|
+
/** A matte given by name: another layer of the same composition. `channel` is how it is read (`'alpha'` by default, or `'luma'`: its brightness); `invert` uses 1 − matte. */
|
|
212
|
+
interface MatteRefSpec {
|
|
213
|
+
layer: string;
|
|
214
|
+
channel?: 'alpha' | 'luma';
|
|
215
|
+
invert?: boolean;
|
|
216
|
+
}
|
|
217
|
+
/** A layer's `mask` can be a layer written in place, the name of another layer, `{ layer, channel, invert }`, or a list of those (intersect). */
|
|
218
|
+
type MaskRefSpec = string | MatteRefSpec;
|
|
202
219
|
interface SequenceCommon {
|
|
203
220
|
name?: string;
|
|
204
221
|
/**
|
|
@@ -239,8 +256,12 @@ interface SequenceCommon {
|
|
|
239
256
|
*
|
|
240
257
|
* Any sequence type works as a mask; shapes are the natural choice for
|
|
241
258
|
* geometric reveals.
|
|
259
|
+
*
|
|
260
|
+
* Instead of a layer written in place, `mask` can name another layer of the same composition (a matte: it is not drawn, and several
|
|
261
|
+
* layers can share it), `{ layer, channel, invert }`, or a list of those (all of them must let the layer through: intersect; subtract
|
|
262
|
+
* with `invert: true`).
|
|
242
263
|
*/
|
|
243
|
-
mask?: SequenceSpec;
|
|
264
|
+
mask?: SequenceSpec | MaskRefSpec | MaskRefSpec[];
|
|
244
265
|
/**
|
|
245
266
|
* When true, the mask is inverted: pixels inside the mask shape become
|
|
246
267
|
* transparent and pixels outside become visible. Useful for "knockout"
|
|
@@ -265,6 +286,10 @@ interface SequenceCommon {
|
|
|
265
286
|
threeD?: boolean;
|
|
266
287
|
/** A threeD layer the camera passes (a fly-through): hide it quietly when it is at or behind the camera instead of warning. Default false. */
|
|
267
288
|
hideBehindCamera?: boolean;
|
|
289
|
+
/** `false` keeps this `threeD` layer unlit when the composition has `light` layers (default true: every threeD layer receives the lights). */
|
|
290
|
+
lit?: boolean;
|
|
291
|
+
/** This `threeD` layer's outline shadows the other lit layers under lights that have `castsShadows`. Default false. */
|
|
292
|
+
castsShadows?: boolean;
|
|
268
293
|
}
|
|
269
294
|
interface VideoSequenceSpec extends SequenceCommon {
|
|
270
295
|
type: 'video';
|
|
@@ -494,6 +519,35 @@ interface CompositionSequenceSpec extends SequenceCommon {
|
|
|
494
519
|
interface CameraSequenceSpec extends SequenceCommon {
|
|
495
520
|
type: 'camera';
|
|
496
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* A light layer. Like the camera it draws nothing; put its props in `initial` / `keyframes`:
|
|
524
|
+
* `x y z lookAtX lookAtY lookAtZ intensity color coneAngle coneFeather radius falloffDistance shadowDarkness shadowDiffusion`.
|
|
525
|
+
*/
|
|
526
|
+
interface LightSequenceSpec extends SequenceCommon {
|
|
527
|
+
type: 'light';
|
|
528
|
+
/** `ambient`: a flat colour everywhere; `point`: from a position in every direction; `spot`: a point limited to a cone around the direction to its look-at point; `parallel`: a direction only (the sun). Default `'point'`. */
|
|
529
|
+
kind?: 'ambient' | 'point' | 'spot' | 'parallel';
|
|
530
|
+
/** How a point or spot light weakens with distance. Default `'none'`. */
|
|
531
|
+
falloff?: 'none' | 'smooth' | 'inverseSquare';
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* A shader layer: a Shadertoy fragment shader drawn into a picture. Everything a layer can do works on it (initial, keyframes, mask, filters,
|
|
535
|
+
* blendMode, threeD, lights).
|
|
536
|
+
*/
|
|
537
|
+
interface ShaderSequenceSpec extends SequenceCommon {
|
|
538
|
+
type: 'shader';
|
|
539
|
+
/** A Shadertoy fragment shader: write `void mainImage(out vec4 fragColor, in vec2 fragCoord)` (GLSL ES 3.00). `iResolution`, `iTime` (this layer's own seconds) and `iFrame` are declared for you, and so is every name in `uniforms`. */
|
|
540
|
+
fragment: string;
|
|
541
|
+
/** Numbers (`float`), arrays of 2–4 numbers (`vec2` … `vec4`) and colours (`'#ff8040'` → `vec3`), animated with keyframes as `'uniforms.speed'` (a component of a vector or a colour: `'uniforms.tint.0'`). */
|
|
542
|
+
uniforms?: Record<string, number | number[] | string>;
|
|
543
|
+
/** Layer size in px (expressions allowed). Default: the composition's size. */
|
|
544
|
+
width?: number | string;
|
|
545
|
+
height?: number | string;
|
|
546
|
+
/** Draw at this fraction of the layer size and scale up (0.5 = a quarter of the pixels). Default 1. */
|
|
547
|
+
resolution?: number;
|
|
548
|
+
/** `true`: the shader's alpha is used (premultiplied). Default `false`: opaque. */
|
|
549
|
+
transparent?: boolean;
|
|
550
|
+
}
|
|
497
551
|
/**
|
|
498
552
|
* A gradient fill for a shape (replaces `fillColor`). Positions are relative to the shape's own
|
|
499
553
|
* bounds (0–1), so it follows the shape's size. Colours may have alpha (`'rgba(0,0,0,0.6)'`), so a
|
|
@@ -635,7 +689,7 @@ type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | Ar
|
|
|
635
689
|
interface NullSequenceSpec extends SequenceCommon {
|
|
636
690
|
type: 'null';
|
|
637
691
|
}
|
|
638
|
-
type SequenceSpec = NullSequenceSpec | VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | ShapeSequenceSpec;
|
|
692
|
+
type SequenceSpec = NullSequenceSpec | VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | LightSequenceSpec | ShaderSequenceSpec | ShapeSequenceSpec;
|
|
639
693
|
/** Top-level composition (root node) — same as `CompositionSequenceSpec` minus the discriminant. */
|
|
640
694
|
interface CompositionSpec extends SequenceCommon {
|
|
641
695
|
width?: number;
|
|
@@ -654,6 +708,8 @@ interface CompositionShape {
|
|
|
654
708
|
width: number;
|
|
655
709
|
height: number;
|
|
656
710
|
duration: number;
|
|
711
|
+
/** The movie's frame rate, on the root shape only (a shader layer's `iFrame` needs it). */
|
|
712
|
+
frameRate?: number;
|
|
657
713
|
}
|
|
658
714
|
/** Audio descriptor pushed to the mixdown queue by AudioSequence/VideoSequence. */
|
|
659
715
|
interface AudioDescriptor {
|
|
@@ -685,4 +741,4 @@ interface AudioDescriptor {
|
|
|
685
741
|
sourceMap?: (u: number) => number;
|
|
686
742
|
}
|
|
687
743
|
|
|
688
|
-
export {
|
|
744
|
+
export { type StopSpec as $, type ArcShapeSpec as A, type BlendModeName as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type PathShapeSpec as H, type ImageSequenceSpec as I, type PolygonShapeSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type NullSequenceSpec as N, type PropValue as O, type Props as P, type SfxKnobs as Q, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type SfxOptions as U, type SfxPreset as V, type SfxVoice as W, type ShaderSequenceSpec as X, type ShapeSequenceSpec as Y, type SlideTransition as Z, type Stop as _, type CameraSequenceSpec as a, type TransitionCommon as a0, type VideoSequenceSpec as a1, type WipeTransition as a2, type ZoomTransition as a3, normalizeStops as a4, pictureStops as a5, type AudioDescriptor as a6, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioMusicSpec as g, type AudioSequenceSpec as h, type AudioSfxSpec as i, type ChromaKeyFilterSpec as j, type CircleShapeSpec as k, type CompositionSequenceSpec as l, type CrossfadeTransition as m, type CustomFilterSpec as n, type DissolveTransition as o, type Expr as p, type IrisTransition as q, type LineShapeSpec as r, type LumaTransition as s, type MaskRefSpec as t, type MatteRefSpec as u, type MusicDrum as v, type MusicDrums as w, type MusicInstrument as x, type MusicLevel as y, type MusicTrack as z };
|
|
@@ -196,9 +196,26 @@ interface DissolveTransition extends TransitionCommon {
|
|
|
196
196
|
/** 0..1 edge softness within each chunk. Default 0.05. */
|
|
197
197
|
smoothing?: number;
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
interface LumaTransition extends TransitionCommon {
|
|
200
|
+
kind: 'luma';
|
|
201
|
+
/** The brightness map the wipe follows: `'linear'` (left to right), `'diagonal'`, `'radial'` (from the middle), or the name of a grayscale image asset (dark parts change first). Default `'linear'`. */
|
|
202
|
+
map?: string;
|
|
203
|
+
/** Width of the soft band between the scenes, in brightness units (0..1). Default 0.1. */
|
|
204
|
+
softness?: number;
|
|
205
|
+
/** Reverse the map (light parts change first). */
|
|
206
|
+
flip?: boolean;
|
|
207
|
+
}
|
|
208
|
+
type TransitionSpec = CrossfadeTransition | WipeTransition | IrisTransition | SlideTransition | DipTransition | ZoomTransition | DissolveTransition | LumaTransition;
|
|
200
209
|
/** How a layer blends with what is behind it: the CSS `mix-blend-mode` names, plus `add` and `linear-burn`. */
|
|
201
210
|
type BlendModeName = 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' | 'hard-light' | 'color-dodge' | 'color-burn' | 'darken' | 'lighten' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'linear-burn';
|
|
211
|
+
/** A matte given by name: another layer of the same composition. `channel` is how it is read (`'alpha'` by default, or `'luma'`: its brightness); `invert` uses 1 − matte. */
|
|
212
|
+
interface MatteRefSpec {
|
|
213
|
+
layer: string;
|
|
214
|
+
channel?: 'alpha' | 'luma';
|
|
215
|
+
invert?: boolean;
|
|
216
|
+
}
|
|
217
|
+
/** A layer's `mask` can be a layer written in place, the name of another layer, `{ layer, channel, invert }`, or a list of those (intersect). */
|
|
218
|
+
type MaskRefSpec = string | MatteRefSpec;
|
|
202
219
|
interface SequenceCommon {
|
|
203
220
|
name?: string;
|
|
204
221
|
/**
|
|
@@ -239,8 +256,12 @@ interface SequenceCommon {
|
|
|
239
256
|
*
|
|
240
257
|
* Any sequence type works as a mask; shapes are the natural choice for
|
|
241
258
|
* geometric reveals.
|
|
259
|
+
*
|
|
260
|
+
* Instead of a layer written in place, `mask` can name another layer of the same composition (a matte: it is not drawn, and several
|
|
261
|
+
* layers can share it), `{ layer, channel, invert }`, or a list of those (all of them must let the layer through: intersect; subtract
|
|
262
|
+
* with `invert: true`).
|
|
242
263
|
*/
|
|
243
|
-
mask?: SequenceSpec;
|
|
264
|
+
mask?: SequenceSpec | MaskRefSpec | MaskRefSpec[];
|
|
244
265
|
/**
|
|
245
266
|
* When true, the mask is inverted: pixels inside the mask shape become
|
|
246
267
|
* transparent and pixels outside become visible. Useful for "knockout"
|
|
@@ -265,6 +286,10 @@ interface SequenceCommon {
|
|
|
265
286
|
threeD?: boolean;
|
|
266
287
|
/** A threeD layer the camera passes (a fly-through): hide it quietly when it is at or behind the camera instead of warning. Default false. */
|
|
267
288
|
hideBehindCamera?: boolean;
|
|
289
|
+
/** `false` keeps this `threeD` layer unlit when the composition has `light` layers (default true: every threeD layer receives the lights). */
|
|
290
|
+
lit?: boolean;
|
|
291
|
+
/** This `threeD` layer's outline shadows the other lit layers under lights that have `castsShadows`. Default false. */
|
|
292
|
+
castsShadows?: boolean;
|
|
268
293
|
}
|
|
269
294
|
interface VideoSequenceSpec extends SequenceCommon {
|
|
270
295
|
type: 'video';
|
|
@@ -494,6 +519,35 @@ interface CompositionSequenceSpec extends SequenceCommon {
|
|
|
494
519
|
interface CameraSequenceSpec extends SequenceCommon {
|
|
495
520
|
type: 'camera';
|
|
496
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* A light layer. Like the camera it draws nothing; put its props in `initial` / `keyframes`:
|
|
524
|
+
* `x y z lookAtX lookAtY lookAtZ intensity color coneAngle coneFeather radius falloffDistance shadowDarkness shadowDiffusion`.
|
|
525
|
+
*/
|
|
526
|
+
interface LightSequenceSpec extends SequenceCommon {
|
|
527
|
+
type: 'light';
|
|
528
|
+
/** `ambient`: a flat colour everywhere; `point`: from a position in every direction; `spot`: a point limited to a cone around the direction to its look-at point; `parallel`: a direction only (the sun). Default `'point'`. */
|
|
529
|
+
kind?: 'ambient' | 'point' | 'spot' | 'parallel';
|
|
530
|
+
/** How a point or spot light weakens with distance. Default `'none'`. */
|
|
531
|
+
falloff?: 'none' | 'smooth' | 'inverseSquare';
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* A shader layer: a Shadertoy fragment shader drawn into a picture. Everything a layer can do works on it (initial, keyframes, mask, filters,
|
|
535
|
+
* blendMode, threeD, lights).
|
|
536
|
+
*/
|
|
537
|
+
interface ShaderSequenceSpec extends SequenceCommon {
|
|
538
|
+
type: 'shader';
|
|
539
|
+
/** A Shadertoy fragment shader: write `void mainImage(out vec4 fragColor, in vec2 fragCoord)` (GLSL ES 3.00). `iResolution`, `iTime` (this layer's own seconds) and `iFrame` are declared for you, and so is every name in `uniforms`. */
|
|
540
|
+
fragment: string;
|
|
541
|
+
/** Numbers (`float`), arrays of 2–4 numbers (`vec2` … `vec4`) and colours (`'#ff8040'` → `vec3`), animated with keyframes as `'uniforms.speed'` (a component of a vector or a colour: `'uniforms.tint.0'`). */
|
|
542
|
+
uniforms?: Record<string, number | number[] | string>;
|
|
543
|
+
/** Layer size in px (expressions allowed). Default: the composition's size. */
|
|
544
|
+
width?: number | string;
|
|
545
|
+
height?: number | string;
|
|
546
|
+
/** Draw at this fraction of the layer size and scale up (0.5 = a quarter of the pixels). Default 1. */
|
|
547
|
+
resolution?: number;
|
|
548
|
+
/** `true`: the shader's alpha is used (premultiplied). Default `false`: opaque. */
|
|
549
|
+
transparent?: boolean;
|
|
550
|
+
}
|
|
497
551
|
/**
|
|
498
552
|
* A gradient fill for a shape (replaces `fillColor`). Positions are relative to the shape's own
|
|
499
553
|
* bounds (0–1), so it follows the shape's size. Colours may have alpha (`'rgba(0,0,0,0.6)'`), so a
|
|
@@ -635,7 +689,7 @@ type ShapeSequenceSpec = RectShapeSpec | CircleShapeSpec | EllipseShapeSpec | Ar
|
|
|
635
689
|
interface NullSequenceSpec extends SequenceCommon {
|
|
636
690
|
type: 'null';
|
|
637
691
|
}
|
|
638
|
-
type SequenceSpec = NullSequenceSpec | VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | ShapeSequenceSpec;
|
|
692
|
+
type SequenceSpec = NullSequenceSpec | VideoSequenceSpec | ImageSequenceSpec | TextSequenceSpec | AudioSequenceSpec | CompositionSequenceSpec | CameraSequenceSpec | LightSequenceSpec | ShaderSequenceSpec | ShapeSequenceSpec;
|
|
639
693
|
/** Top-level composition (root node) — same as `CompositionSequenceSpec` minus the discriminant. */
|
|
640
694
|
interface CompositionSpec extends SequenceCommon {
|
|
641
695
|
width?: number;
|
|
@@ -654,6 +708,8 @@ interface CompositionShape {
|
|
|
654
708
|
width: number;
|
|
655
709
|
height: number;
|
|
656
710
|
duration: number;
|
|
711
|
+
/** The movie's frame rate, on the root shape only (a shader layer's `iFrame` needs it). */
|
|
712
|
+
frameRate?: number;
|
|
657
713
|
}
|
|
658
714
|
/** Audio descriptor pushed to the mixdown queue by AudioSequence/VideoSequence. */
|
|
659
715
|
interface AudioDescriptor {
|
|
@@ -685,4 +741,4 @@ interface AudioDescriptor {
|
|
|
685
741
|
sourceMap?: (u: number) => number;
|
|
686
742
|
}
|
|
687
743
|
|
|
688
|
-
export {
|
|
744
|
+
export { type StopSpec as $, type ArcShapeSpec as A, type BlendModeName as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type PathShapeSpec as H, type ImageSequenceSpec as I, type PolygonShapeSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type NullSequenceSpec as N, type PropValue as O, type Props as P, type SfxKnobs as Q, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type SfxOptions as U, type SfxPreset as V, type SfxVoice as W, type ShaderSequenceSpec as X, type ShapeSequenceSpec as Y, type SlideTransition as Z, type Stop as _, type CameraSequenceSpec as a, type TransitionCommon as a0, type VideoSequenceSpec as a1, type WipeTransition as a2, type ZoomTransition as a3, normalizeStops as a4, pictureStops as a5, type AudioDescriptor as a6, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioMusicSpec as g, type AudioSequenceSpec as h, type AudioSfxSpec as i, type ChromaKeyFilterSpec as j, type CircleShapeSpec as k, type CompositionSequenceSpec as l, type CrossfadeTransition as m, type CustomFilterSpec as n, type DissolveTransition as o, type Expr as p, type IrisTransition as q, type LineShapeSpec as r, type LumaTransition as s, type MaskRefSpec as t, type MatteRefSpec as u, type MusicDrum as v, type MusicDrums as w, type MusicInstrument as x, type MusicLevel as y, type MusicTrack as z };
|
package/docs/dsl.md
CHANGED
|
@@ -467,6 +467,35 @@ For rect / circle / ellipse, `anchorX` / `anchorY` (default `0.5` each; not avai
|
|
|
467
467
|
|
|
468
468
|
---
|
|
469
469
|
|
|
470
|
+
### `shader`
|
|
471
|
+
|
|
472
|
+
A fragment shader as a layer: write a Shadertoy `mainImage` and it is drawn into a picture that is a layer like any other (it moves, fades, masks, filters, blends, goes into 2.5D and receives lights). Use it for what is drawn by a formula: flowing colour fields, plasma, noise, waves, patterns.
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
{ type: 'shader', name: 'plasma',
|
|
476
|
+
fragment: `
|
|
477
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
478
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
479
|
+
float v = sin(uv.x * 10.0 + iTime) + sin(uv.y * 8.0 - iTime * 1.3);
|
|
480
|
+
fragColor = vec4(mix(tint, vec3(0.1, 0.0, 0.3), 0.5 + 0.25 * v), 1.0);
|
|
481
|
+
}`,
|
|
482
|
+
uniforms: { tint: '#ff8040', speed: 1 }, // declared for you: `uniform vec3 tint;` and `uniform float speed;`
|
|
483
|
+
keyframes: [{ at: 0, to: { 'uniforms.speed': 3, 'uniforms.tint.2': 0.8 }, duration: 4 }] }
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Fields: `fragment` (the GLSL, required), `uniforms` (below), `width` / `height` (px, expressions allowed; default the composition's size), `resolution` (default 1; `0.5` draws a quarter of the pixels and scales up: the way out for a heavy shader), `transparent` (default `false`), and everything every layer has.
|
|
487
|
+
|
|
488
|
+
Rules:
|
|
489
|
+
1. **Write `mainImage`** — `void mainImage(out vec4 fragColor, in vec2 fragCoord)`, GLSL ES 3.00. `fragCoord` is in the layer's pixels, origin at the **bottom left** (Shadertoy's convention). `iResolution` (a `vec3`: width, height, 1), **`iTime`** (the layer's own seconds, from 0 at its start; inside a time-remapped composition, that composition's clock) and `iFrame` (an `int`) are declared for you, and so is every name in `uniforms`. Do not write `#version`, `precision`, `main()`, `gl_FragColor` (write the colour into `fragColor`), `texture2D` (it is `texture`), `varying` or `attribute`: each warns with what to write.
|
|
490
|
+
2. **`uniforms`**: a number is a `float`, 2 to 4 numbers are a `vec2` / `vec3` / `vec4`, a colour `'#rrggbb'` is a `vec3` in 0..1. Keyframes move them as `'uniforms.speed'`; a component of a vector or a colour as `'uniforms.tint.0'` (0 = x or red). A name or index that does not exist warns once with the likely name.
|
|
491
|
+
3. **Opaque by default**: the alpha you write is ignored. `transparent: true` uses it (the shader's colour is premultiplied by its alpha, so soft edges blend over what is behind).
|
|
492
|
+
4. **It is a layer, not a filter**: it cannot read what is behind it, and this version has no `iChannel` textures, `iMouse`, `iDate` or `iTimeDelta` (using one warns). One pass only: no buffers or feedback.
|
|
493
|
+
5. **A compile error is said in your own line numbers** (`line 2: 'x' : undeclared identifier`) and the layer is drawn as a magenta checkerboard until it is fixed, so a mistake is never a silent blank layer.
|
|
494
|
+
6. **The GPU cannot be stopped**: a `while` loop, or a `for` whose bound is not a constant, can hang it, so both warn. Give loops a constant bound and `break` early.
|
|
495
|
+
7. **Deterministic, with one caveat**: a frame is a pure function of `iTime`, `iFrame` and the uniforms, so playback, seeking and export agree. Different GPUs can round differently, so an export from another machine can differ in the last bits.
|
|
496
|
+
8. **Size and place**: with `width` / `height` smaller than the composition the layer's **top-left corner** sits at `initial.x` / `initial.y` (like an image; a shader layer has no anchor to set). **A mask or matte cuts the picture the shader drew over its whole size, it does not move it**: a ripple centred in the shader's own frame stays there, so to show the middle of a pattern through a disc at x = 410, put the pattern's centre at x = 410 (a `vec2` uniform: `uniforms: { centre: [410, 360] }`) or size and place the layer to match. With `resolution` below 1, `iResolution` and `fragCoord` are in the **scaled** buffer's pixels (half size at 0.5), so a pixel-unit uniform (a centre, a radius) must be multiplied by `resolution`; `fragCoord / iResolution.xy` needs no correction.
|
|
497
|
+
9. **Cost** (a prototype measurement at 1080p, on one machine, an Apple M1 Pro; the released layer is not re-measured): a 160×90 shader with an 8-step loop, 20 layers, cost 11 ms (WebGPU) to 17 ms (WebGL) a frame more than 20 plain rectangles; one full-size 1920×1080 layer cost about 10 ms more. Every layer pays a fixed cost (about 0.5 to 0.85 ms), so use a few big shaders rather than many small ones, and `resolution` for heavy ones. All shader layers share **one** WebGL2 context (so there is no limit from the browser's number of contexts); a shader layer needs WebGL2 (without it the build warns and draws the checkerboard).
|
|
498
|
+
|
|
470
499
|
## 3D layers & camera
|
|
471
500
|
|
|
472
501
|
Place any 2D layer in depth and view it through a camera — After Effects style, no three.js. Everything uses the normal `initial` / `keyframes` / expression vocabulary.
|
|
@@ -588,11 +617,63 @@ A focus pull is a normal keyframe on `focus`; a layer name works there too:
|
|
|
588
617
|
- A negative or non-numeric `aperture` warns once and turns depth of field off.
|
|
589
618
|
- `movie.inspect(frame)` reports each `threeD` layer's blur as `depthBlur` (px, 0 = sharp).
|
|
590
619
|
|
|
620
|
+
### Lights
|
|
621
|
+
|
|
622
|
+
A `light` layer lights the `threeD` layers of its composition: they get darker and brighter with their angle and distance, like cards in a dark room. It is a layer like the camera: it draws nothing, and its numbers go in `initial` and `keyframes` (so it moves, springs, takes expressions and `ease`).
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
{ type: 'light', kind: 'ambient', initial: { intensity: 0.2 } }, // what no other light reaches is this dark, not black
|
|
626
|
+
{ type: 'light', kind: 'spot', castsShadows: true, // a spotlight that crosses the picture
|
|
627
|
+
initial: { x: 160, y: -120, z: 700, lookAtX: 260, lookAtY: 330, lookAtZ: 0, coneAngle: 34, coneFeather: 0.7, intensity: 1.1 },
|
|
628
|
+
keyframes: [{ at: 0.5, to: { x: 1100, lookAtX: 1020 }, duration: 4.5, ease: 'sine.inOut' }] },
|
|
629
|
+
{ type: 'shape', shape: 'rect', name: 'card', width: 300, height: 200, threeD: true, castsShadows: true, initial: { x: 300, y: 300, z: 0, fillColor: '#e0a458' } },
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
`kind` (a fixed setting on the layer): `'ambient'` (the same colour everywhere), `'point'` (from a position in every direction; the default), `'spot'` (a point light inside a cone around the direction to its look-at point), `'parallel'` (a direction only, from the position toward the look-at point: the sun). `falloff` (also fixed): `'none'` (default), `'smooth'` (full inside `radius`, fading to nothing over `falloffDistance` more), `'inverseSquare'` (full at `radius`, a quarter at twice that). Numbers you can animate:
|
|
633
|
+
|
|
634
|
+
| name | meaning | default |
|
|
635
|
+
|---|---|---|
|
|
636
|
+
| `x` `y` `z` | where the light is (px, like a layer; `z` toward the viewer) | up and to the left of the picture, 0.6 of the camera's distance |
|
|
637
|
+
| `lookAtX` `lookAtY` `lookAtZ` | the point a `spot` or `parallel` light points at | the middle of the picture |
|
|
638
|
+
| `intensity` | a multiplier: 1 = full, 0.25 = a quarter (not a percentage) | 1 |
|
|
639
|
+
| `color` | any colour (`'#ffd9a0'`) | white |
|
|
640
|
+
| `coneAngle` `coneFeather` | a spot's full angle in degrees, and the soft part of its edge as a fraction of the half angle (0 = hard, 1 = soft all the way in) | 90, 0.5 |
|
|
641
|
+
| `radius` `falloffDistance` | where the falloff starts and how far it runs (px) | 500, 500 |
|
|
642
|
+
| `shadowDarkness` `shadowDiffusion` | how dark the shadows of this light are (0..1) and how soft their edge is (px; 0 = hard) | 1, 0 |
|
|
643
|
+
|
|
644
|
+
Rules:
|
|
645
|
+
1. **A composition with a `light` layer lights every `threeD` layer in it.** To keep one unlit, write `lit: false` on it. Layers without `threeD` are never lit. **A composition with no light and no fog makes nothing new: the shader is not built and the picture and the cost do not change.**
|
|
646
|
+
2. **What no light reaches is black.** Add an ambient light (the build warns when there is none). A white ambient light at intensity 1 is no light at all: the picture is the one you would get without lights. A light behind a layer does not light the side the camera sees.
|
|
647
|
+
3. **A light counts only while it is alive** (its own `at` and `duration`). With no light alive at a moment, the layers are drawn unlit, as in a composition without lights; so give the lights the length of the scene.
|
|
648
|
+
4. **Each composition has its own lights.** A light inside a nested composition lights only that composition's `threeD` layers; an outer light does not reach inside it. A `threeD` composition (a card) is lit as one picture by its parent's lights.
|
|
649
|
+
5. **Layers are drawn in distance order and there is no depth buffer.** Planes that cross each other cannot be shown; for a floor or a wall, put the layer's origin at its far edge so it sorts behind what stands on it.
|
|
650
|
+
6. **Shading works on the sRGB values** (like After Effects): a strong light clips to white. The shading is `colour × (ambient + the lights)`, each light by `N·L`, its falloff and its cone.
|
|
651
|
+
7. **Order:** the layer's own `filters` run first (they draw into the layer's picture), then the shading, the fog, the depth-of-field blur, and last the `blendMode`. All of them work together.
|
|
652
|
+
8. At most 8 lights besides the ambient ones are used (the first 8 in layer order; the build says which are left out). Ambient lights are added up.
|
|
653
|
+
9. `color` keyframes mix in sRGB like other colour keyframes (`'#ff0000'` → `'#0000ff'` passes `#800080`). A light has no `colorSpace`.
|
|
654
|
+
10. `movie.inspect(frame)` gives each lit `threeD` layer a `light`: how lit its middle is (1 = as bright as with no light, 0 = black); `check.mjs --at` prints the most lit first. It is the number at the layer's **middle** and shadows are not in it, so it can say 1 for a layer whose lit part is hidden.
|
|
655
|
+
11. **A spot's pool on a wall** is a circle of radius about `distance × tan(coneAngle / 2)` around where its axis meets the wall (a 36° cone from 700 px away lights a circle about 230 px in radius), softened by `coneFeather`. A card in front of the wall throws its shadow on the far side from the light and can cover the pool behind it: make the cards smaller than the pool, or bring the lamp closer. A caster outside the pool still throws its shadow into it. A **floor** is a big layer with `rotationX: 90` and its origin at its far edge (`anchorY: 0`).
|
|
656
|
+
|
|
657
|
+
Checked against an independent JS reference of the shading (every pixel within 2/255) on WebGPU and WebGL, for ambient, point, spot and parallel lights, the three falloffs, a card turned on two axes, and a card seen from its back. Cost, measured at 1080p on one machine (an Apple M1 Pro, headless Chrome) as the extra time per frame against the same scene without light: lights alone cost about 1 ms on WebGPU (20 layers, 3 lights: 2.6 ms) and about the same on WebGL; shadows are the price (below).
|
|
658
|
+
|
|
659
|
+
### Shadows
|
|
660
|
+
|
|
661
|
+
A shadow needs both halves: the **light** has `castsShadows: true` and the **layer** has `castsShadows: true`. Then the layer's outline (its own picture's alpha) darkens the other lit `threeD` layers behind it, on the side away from the light.
|
|
662
|
+
|
|
663
|
+
- Hard edges by default (a hard shadow can read as a dark slab: `shadowDiffusion` of 10 to 20 usually looks right). `shadowDiffusion` (px, on the light) widens the edge, wider the farther the shadow falls from the caster, so a card near the wall has a sharp shadow and one far from it a soft one. `shadowDarkness` (0..1) sets how dark.
|
|
664
|
+
- A layer receives shadows from at most **4** casters (the first 4 in layer order; the build says which are left out), never from itself, and a caster that is hidden (outside its life, alpha 0, behind the camera) casts nothing. Several shadows join by the strongest, not by adding up, so overlaps are not darker.
|
|
665
|
+
- A shadow is the shadow of a flat picture: a card turned on its axis casts the turned outline; there is no self-shadowing, no shadow on a layer that is not lit (`lit: false`), no coloured or see-through shadow.
|
|
666
|
+
- **Soft shadows are the expensive part** (16 samples per pixel per caster). Measured at 1080p, extra ms per frame (WebGPU / WebGL): 5 layers, 1 light, hard 1.4 / 1.6, soft 4.6 / 6.1; 20 layers, 1 light, hard 0.8 / 0.1, soft 5.3 / 10.4; 20 layers, 3 lights, hard 2.6 / 8.7, **soft 19.8 / 21.6**. Three or more lights with soft shadows warn; give `shadowDiffusion` to one or two lights.
|
|
667
|
+
|
|
668
|
+
### Fog
|
|
669
|
+
|
|
670
|
+
The camera takes `fogNear`, `fogFar` (distances along the view direction, px), `fogColor` and `fogAmount` (0..1, how much of the colour the farthest things get). Writing any one of them turns fog on (the others default to: near = the camera's home distance, far = three times that, black, 1). A `threeD` layer is mixed toward the fog colour by its depth, per pixel (so a floor going away gets a true gradient). It comes after the shading and before the depth-of-field blur. 2D layers are not fogged: draw the background in the fog colour so what is far dissolves into it. Without lights, fog alone costs almost nothing. `fogFar` not beyond `fogNear`, or an amount outside 0..1, warns.
|
|
671
|
+
|
|
591
672
|
### Depth order and limits
|
|
592
673
|
|
|
593
674
|
- Consecutive `threeD` layers are drawn farthest-first. A non-`threeD` layer between them splits the group (like After Effects).
|
|
594
675
|
- A layer at or behind the camera is hidden for that frame.
|
|
595
|
-
- v1 limits: planes do not intersect (whole layers are sorted); masks, transitions and `filterArea` are not supported on `threeD` layers (a mask is ignored with a warning); each `threeD` layer costs one extra render pass per frame.
|
|
676
|
+
- v1 limits: planes do not intersect (whole layers are sorted; with lights this shows when you build a room: see [Lights](#lights)); masks, transitions and `filterArea` are not supported on `threeD` layers (a mask is ignored with a warning); each `threeD` layer costs one extra render pass per frame.
|
|
596
677
|
- Wrong-but-likely names (`rotateY`, `translateZ`, `depth`, `perspective`, `zoom`) are not accepted; the console tells you the right name.
|
|
597
678
|
|
|
598
679
|
---
|
|
@@ -706,8 +787,8 @@ The mask is itself a sequence, so it can have `keyframes` of its own — useful
|
|
|
706
787
|
|
|
707
788
|
Notes:
|
|
708
789
|
- The mask sequence is rendered as a mask, not as a normal child — its `fillColor` / `strokeColor` only matter for which pixels are kept, not for the visible colour.
|
|
709
|
-
- Any sequence type works as a mask (shape / image / text / nested composition); shapes are the natural choice for geometric reveals.
|
|
710
|
-
- A **
|
|
790
|
+
- Any sequence type works as a mask (shape / image / text / video / nested composition); shapes are the natural choice for geometric reveals.
|
|
791
|
+
- A plain **shape** mask (not inverted) is a hard-edged stencil, the cheapest way. Every other mask (a text, image or video layer, `maskInverted`, a named matte, a list) is drawn into a texture once per frame and applied as a **matte filter**: its edges are soft and antialiased, it costs one offscreen pass, and `blendMode` works with it (see [Mattes](#named-mattes-and-luma)).
|
|
711
792
|
- For a left-to-right wipe, give the mask `anchorX: 0` (rect/circle/ellipse) so `width: 0 → full` grows rightward from the left edge.
|
|
712
793
|
|
|
713
794
|
#### `maskInverted`
|
|
@@ -731,7 +812,41 @@ When `true`, flip the mask sense: pixels INSIDE the mask shape become transparen
|
|
|
731
812
|
}
|
|
732
813
|
```
|
|
733
814
|
|
|
734
|
-
|
|
815
|
+
An inverted mask goes through the matte filter (the letters of a text mask, or a hole with a soft edge), so it works with every `blendMode` and with a mask that has its own `filters`. `maskInverted` is the same thing as `invert: true` on a named matte; with a named matte, write `invert: true` (below).
|
|
816
|
+
|
|
817
|
+
#### Named mattes and luma
|
|
818
|
+
|
|
819
|
+
A layer can be named and used as the mask of **several** layers (After Effects' track matte). Write its name as the `mask`; the matte layer itself is then **not drawn**. It is drawn once per frame into a texture, so three layers sharing one moving matte cost one extra pass, not three.
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
// one moving band cuts three layers; the band itself is not on screen
|
|
823
|
+
{ type: 'shape', shape: 'rect', name: 'wipe', anchorX: 0, width: 0, height: 420,
|
|
824
|
+
initial: { x: 60, y: 300 }, keyframes: [{ at: 0.3, to: { width: 560 }, duration: 1.5 }] },
|
|
825
|
+
{ type: 'text', text: 'SHARED', mask: 'wipe' },
|
|
826
|
+
{ type: 'text', text: 'MASK', at: 0.2, mask: 'wipe' },
|
|
827
|
+
{ type: 'composition', width: 400, height: 300, mask: 'wipe', sequences: [ /* a whole picture, cut as one */ ] },
|
|
828
|
+
|
|
829
|
+
// by brightness instead of opacity (a grayscale image or a gradient layer), inverted, and a list
|
|
830
|
+
{ type: 'image', asset: 'photo', mask: { layer: 'vignette', channel: 'luma' } },
|
|
831
|
+
{ type: 'shape', shape: 'rect', width: 400, height: 300, mask: ['panel', { layer: 'hole', invert: true }] }, // inside `panel`, outside `hole`
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
| form | meaning |
|
|
835
|
+
|---|---|
|
|
836
|
+
| `mask: 'name'` | the same as `{ layer: 'name' }` |
|
|
837
|
+
| `mask: { layer, channel?, invert? }` | `channel: 'alpha'` (default: how opaque the matte is) or `'luma'` (how bright it is, Rec. 709); `invert: true` flips it (1 − matte) |
|
|
838
|
+
| `mask: [ref, ref, …]` | every one must let the pixel through: the result is the **product** (an intersection). **To subtract a matte, put `invert: true` on it** |
|
|
839
|
+
| `mask: { type: …, … }` | an inline mask layer, as above |
|
|
840
|
+
|
|
841
|
+
Rules:
|
|
842
|
+
1. **Outside the matte layer's own shape the matte is 0, for `luma` too** (nothing there is transparent and black), so a matte can be a wide gradient rectangle that you slide across the layers for a soft, moving reveal. **A layer that is used as a matte is not drawn.** It still needs to live long enough: the matte's time is the composition's time (its own `at` and `duration`). Where the matte is missing, the layers it cuts are **invisible**, so a matte whose lifetime does not cover its users warns.
|
|
843
|
+
2. **The name must be in the same composition** as the layer that uses it (a layer inside a nested composition cannot name a matte outside it). A layer that a transition wraps keeps its matte: the transition carries the `mask` to the wrapper.
|
|
844
|
+
3. A matte with a `parent` follows its parents (the null layers) as any layer does. A masked layer with a `parent` warns: the matte is in the composition's space, the layer in the null's space; put the mask on the null instead.
|
|
845
|
+
4. `threeD` layers cannot be mattes and cannot have one (a warning; the matte is drawn as the `threeD` layer it is, the mask is ignored).
|
|
846
|
+
5. **`blendMode` works with every matte**: the layer is cut by the matte and then blended (inside the matte it blends with the backdrop, outside it is the backdrop). A matte on a composition cuts the composition as one picture, as any filter does (its children's blends happen inside that picture).
|
|
847
|
+
6. A matte inside a **rotated or scaled nested composition** differs from the stencil only at the edges (soft instead of hard).
|
|
848
|
+
7. **Cost** (a prototype measurement at 1080p; not yet re-measured on the released code): WebGPU 0.32 ms for one layer, 0.75 ms for three, 2.65 ms for ten (0.67 ms and 1.97 ms when the layers share one matte); WebGL 1.22 / 4.03 / 11.0 ms. One matte is a texture of the composition's size (about 8 MiB at 1080p, more with anti-aliasing): **more than 8 mattes in one composition warns**. Share a matte between layers, or use fewer.
|
|
849
|
+
8. `inspect` does not list a matte layer (it is not drawn), and does not clip a masked layer's box by a named matte.
|
|
735
850
|
|
|
736
851
|
#### `blendMode`
|
|
737
852
|
|
|
@@ -762,7 +877,7 @@ Rules:
|
|
|
762
877
|
7. **Together with `filters`, `threeD` and depth of field they all work** (the blend is applied after the layer's own filters, and after the depth-of-field blur).
|
|
763
878
|
8. **Some modes only act on some tones.** `overlay` and `soft-light` change almost nothing over a near-black or a near-white backdrop (their formulas leave 0 and 1 where they are), so a colour cast needs a **mid-tone** picture under it; `color-dodge` over a dark backdrop gives a saturated colour (each channel is lifted separately), over a bright one it burns out to white; `multiply` can only darken and `screen` / `add` only brighten.
|
|
764
879
|
9. **`blendMode` is a setting of the layer, not an animatable property** (`initial` or a keyframe with it warns). To change the mode for a moment, put a second, short-lived layer (`at` / `duration`) on top, or fade a layer's `alpha`.
|
|
765
|
-
10. **With `mask`:**
|
|
880
|
+
10. **With `mask`:** every mask works with every mode: the layer is cut by the mask first, then blended. A plain shape mask (not inverted) is a stencil as before; a text, image or video mask, an inverted mask and a named matte go through the matte filter (see [Named mattes and luma](#named-mattes-and-luma)), and the blend is applied after it.
|
|
766
881
|
|
|
767
882
|
Checked against the W3C formulas on WebGPU and WebGL: every pixel within 2/255 (measured worst: 0.5/255 at alpha 1, 1.5/255 at alpha 0.5; over opaque backdrops and over nothing, as inside a `threeD` composition: a half-transparent layer over a half-transparent backdrop can differ more, because Pixi lays the blended result over the backdrop once more); on the same machine a frame is the same picture however it was reached. Another GPU can differ by a step or two.
|
|
768
883
|
|
|
@@ -1124,6 +1239,22 @@ Pixel-grain noise reveal driven by deterministic 2D Perlin noise. Pixels with a
|
|
|
1124
1239
|
}
|
|
1125
1240
|
```
|
|
1126
1241
|
|
|
1242
|
+
### `luma`
|
|
1243
|
+
|
|
1244
|
+
A wipe that follows a **brightness map** (After Effects' Gradient Wipe): the dark parts of the map change first, the light parts last. `to` appears through a soft edge; `from` holds until `to` has covered it (a hard cutoff, as `wipe` and `iris` do).
|
|
1245
|
+
|
|
1246
|
+
```ts
|
|
1247
|
+
{
|
|
1248
|
+
kind: 'luma', from: 'A', to: 'B', at: 4, duration: 1,
|
|
1249
|
+
map: 'radial', // 'linear' (left to right, default), 'diagonal', 'radial' (from the middle, a circle on screen),
|
|
1250
|
+
// or the name of a grayscale image asset (dark parts change first)
|
|
1251
|
+
softness: 0.1, // width of the soft band, in brightness units (0..1). Default 0.1.
|
|
1252
|
+
flip: false, // true = reverse the map (light parts first)
|
|
1253
|
+
}
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
The three built-in maps need no image (they are computed on the GPU from the position on screen). A `map` name that is neither a built-in nor a loaded asset warns once (with the likely name) and wipes as if the map were white. The map covers the whole composition, so give it the composition's aspect ratio.
|
|
1257
|
+
|
|
1127
1258
|
---
|
|
1128
1259
|
|
|
1129
1260
|
## Filters
|
|
@@ -1179,6 +1310,7 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
|
|
|
1179
1310
|
| `rgbSplit` | `red green blue` (each `{ x, y }` offset) | Chromatic aberration |
|
|
1180
1311
|
| `oldFilm` | `sepia noise scratch scratchDensity vignetting seed` | Aged film |
|
|
1181
1312
|
| `glitch` | `slices offset direction seed average` | Digital glitch |
|
|
1313
|
+
| `warp` | `kind: 'wave' \| 'haze'`, `strength` (max shift, px, 6), `scale` (wavelength or cell size, px, 80), `speed` (0.5), `angle` (degrees: the way a wave travels, 0), `seed` (`haze`) | Bends the picture: a travelling wave (flags, water) or drifting noise (heat haze). This library's own, time-driven |
|
|
1182
1314
|
| `bulgePinch` | `center radius strength` | Bulge (+) / pinch (−) |
|
|
1183
1315
|
| `twist` | `radius angle offset: { x, y }` | Swirl |
|
|
1184
1316
|
| `zoomBlur` | `strength center innerRadius radius` | Radial zoom streaks |
|
|
@@ -1203,6 +1335,7 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
|
|
|
1203
1335
|
|
|
1204
1336
|
Gotchas, all measured:
|
|
1205
1337
|
|
|
1338
|
+
- **Distortion:** `warp` (`wave`: a flag or the surface of water, `haze`: heat shimmer; moves with the frame's time, no keyframes needed, and pads the layer by `strength` so a shifted edge is not cut), `twist` (a swirl), `bulgePinch` (a lens), `shockwave` (a ring that spreads), `rgbSplit` (colour fringes), `glitch` (slices). `warp`'s time is the time of the frame (inside a time-remapped composition, that composition's clock), `speed: 0` stands still, `strength` is in px and is capped at 200. It bends the layer's picture; to draw something that does not exist yet, use a `shader` layer.
|
|
1206
1339
|
- **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
|
|
1207
1340
|
- **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
|
|
1208
1341
|
- **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) do not tween as a whole; animate `'filters.<name>.red.x'` if the filter exposes it, or swap the filter. `pixelate` animates through `sizeX` / `sizeY`, not `size`.
|