vaweGitHub

MovesChange between shots

Luma matte dissolve

look: dusk1.5 sluma-matte-dissolve.md

Luma matte dissolve: final frame of the transition animation
Tap the clip to play it.

When to use

Use when the next shot should arrive through its own light: its bright areas appear first, its midtones follow and its dark ground fills in last, so a lit subject grows out of the previous shot along its own contours. The matte is not a shape: it is the next shot's luminance, thresholded by a value that falls from above white to below black. The detail that sells it is that the edge is the picture: a lamp starts as a white point and widens along its own falloff, and the headline (white) is up before the ground (near black). Clip: luma-matte-dissolve.mp4. Demo: demo/luma-matte-dissolve.html.

The code

html
<filter id="luma" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
  <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.2126 0.7152 0.0722 0 0" result="L"/>
  <feComponentTransfer in="L" result="M"><feFuncA id="ramp" type="linear" slope="8" intercept="-8"/></feComponentTransfer>
  <feComposite in="SourceGraphic" in2="M" operator="in"/>
</filter>

Easing and timing

The clip runs 1.5 s. The look is dusk.

The clip uses 2 named eases from core/motion/presets.js. Each is a pair of After Effects handles: influence is how far along the segment the handle reaches, and speed is how fast the value moves at the key, as a multiple of the segment's average speed. Motion in the docs explains both.

EaseLeaving a keyArriving at a key
land12% influence, speed 4.875% influence, speed 0
leave33% influence, speed 060% influence, speed 0

Notes

js
const S = 8, t0 = 0.3, dur = 1.0;                       // S: the edge is 1/S of the luminance range wide
const sweep = (u) => 1 - (1 - u) ** 1.7;
shotB.style.filter = 'url(#luma)';                      // shot A sits under it and dims to 0.55 and 0.96x
vawe.onFrame((t) => {
  const u = Math.min(1, Math.max(0, (t - t0) / dur)), th = 1 + 1 / S - (1 + 2 / S + 0.02) * sweep(u);
  ramp.setAttribute('intercept', (-S * th).toFixed(4));   // alpha = S x (luminance - th): visible where the pixel is brighter than th
});

The threshold starts above white plus one edge width and ends below black minus one edge width, so the first frame shows nothing and the last shows everything, with no pop. Shot B needs a spread of brightness to read: a white subject, a mid-tone falloff and a dark ground. A flat, evenly lit shot dissolves as one block, and a shot that is bright everywhere arrives on the first frames. Give the curve a power near 1.7 and not an exponential: the dark ground holds most of the pixels, and a curve that idles at the end leaves them undecided for too long. Read the filter in sRGB (color-interpolation-filters), or the linear-light luminance shifts every threshold.

Looks (ground, type, radius, accent) live in demo/demo.css; this snippet keeps neutral tokens.

Sound

Sound: none; the next shot arrives through its own light, and a soft cut stays soft.