1 The idea
Symptom: merging a client element darkened the whole frame slightly, even where the element is empty. The rock shadows went pure black. Probe the "Log decode (the file)" node in an empty area: rgb reads −0.014 while alpha is 0.
Cause: the element was premultiplied on its log values and saved as a log file, so its empty pixels sit below the log black code and decode to just below 0 in linear. Merge over adds A straight on (A + B·(1 − a)), so the negative pushes the whole plate down. The three nodes at the top stand in for that file; in real work you can’t change the file, you fix it after the Read.
fix: rgb + k·(1 − a)2 Hands-on in the mini compositor
Task: probe the element’s empty area and enter the cancelling value in "Constant k (fix)" (r, g, b)
Leave the file nodes (Constant log black, Log decode) as they are. The lower Merge plus already uses (1 − a) as its mask.
3 Deep dive
Where the negative comes from
A log file stores black at a code value above 0. Premultiply correctly in linear and then encode to log, and the empty pixels land exactly on the black code and decode back to 0. The trouble starts when the element is premultiplied on its log-encoded values (premultiplied in log): the empty pixels end up at code 0, below the black code, and decoding that back to linear lands slightly below 0. On real jobs this measured about −0.009 to −0.014, depending on the file and the log curve.
It’s too small to see on the element alone, but Merge over adds it to the whole frame, turning any plate shadow below 0.014 pure black.
- 1Premultiplied log file
- 2Read decodes to linear
- 3Empty = −0.014
- 4Over adds it everywhere
Why the fix is rgb + k·(1 − a)
The shortcut "if a = 0, set colour to 0" fixes only fully empty pixels. Soft edges with alpha between 0 and 1 still carry a share of the negative, leaving a thin dark edge.
Adding k·(1 − a) gives back the offset in proportion to emptiness: the full k at a = 0, nothing at a = 1. The solid part keeps its exact colour and the soft edges come out clean.
Measure k for every file; never copy it from another shot
Prevent it: preflight every element
Before using anyone else’s element, sample an empty pixel (rgb and a must all be 0), view the alpha at gain ×4 for baked-in shadows or smudges, and check whether it really moves or repeats one frame. These turn up often in files exported from other apps.
4 In real work
In Nuke
- Read element → Expression: r + kblack*(1-a) (same for g and b) → Merge over
- Name your user knob so it can’t clash with a channel: a knob called g in an Expression reads the green channel instead
- Find k by sampling the element’s empty area at the Read’s output, not after a key or grade
- Check: where the element is empty, the comp must equal the plate (a Merge difference reads 0)
Common mistakes → the fix
- ✗ Stopping at "a == 0 ? 0 : r"✓ Soft edges keep the negative; use rgb + k·(1 − a).
- ✗ Measuring k after a key or keymix✓ Measure at the Read itself; later nodes can blend it into a wrong value.
- ✗ Blaming the plate for the darker frame✓ Compare the plate before and after the merge; a difference in empty areas means the element.
- ✗ Using one k for every shot✓ k depends on the file and log curve; measure each time.
Before you send the shot
- Every element’s empty area reads 0 in rgb and a
- Where the element is empty, comp = plate
- No dark line on the element’s soft edges
- No baked-in shadows or smudges in the alpha (gain ×4)
5 Check yourself
The element is empty over most of the frame, yet the whole frame got darker. Why?
🔒 Answer the question above and this card opens
1. An element’s empty pixels read rgb −0.010, alpha 0. How much do you add on an edge pixel with alpha 0.5?
2. Why does a negative in the empty area darken the whole frame?
Where the numbers come from: The −0.014 comes from a real measurement on a premultiplied log element on one job (rounded to 3 places); the true value varies per file. Here the file is simulated with Constant + Invert + Merge from to give rgb − k·(1 − a). Merge, Invert and Constant match Nuke 17.0v2.