From 785077f6ff0847527a43fb480a05b60af7a29013 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 10 Jun 2026 10:57:38 -0700 Subject: [PATCH] fix(engine): paint ancestor background before drawElementImage capture drawElementImage only paints the captured [data-composition-id] subtree. Compositions that set their background on / (the common authoring pattern) lost it: those canvas pixels stayed transparent and the jpeg encode turned them black. Comps layering semi-transparent elements over the body background (e.g. rgba cards) rendered darkened. Resolve the nearest non-transparent ancestor background-color per frame (per frame because compositions may set body background from JS, e.g. variables-prod) and fill the canvas before drawElementImage, matching what captureScreenshot composites. Fast-vs-baseline PSNR on macOS GPU: font-variant-numeric 23.6 -> 65.4 dB animejs-adapter 25.4 -> 65.5 dB parallel-capture-regression 27.3 -> 53.3 dB css-spinner-render-compat 30.8 -> 56.4 dB variables-prod 30.9 -> 70.5 dB gsap-letters-render-compat 30.9 -> 55.1 dB No regression on comps with in-subtree backgrounds (sub-comp-t0 61.9, many-cuts 62.3 unchanged). Co-Authored-By: Claude Fable 5 --- .../engine/src/services/drawElementService.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/engine/src/services/drawElementService.ts b/packages/engine/src/services/drawElementService.ts index 027673ac7e..3daa3434c8 100644 --- a/packages/engine/src/services/drawElementService.ts +++ b/packages/engine/src/services/drawElementService.ts @@ -171,6 +171,25 @@ export async function captureDrawElementFrame( settled = true; try { ctx.clearRect(0, 0, w, h); + // drawElementImage only paints the captured subtree. A background + // set on / (the common authoring pattern) lives OUTSIDE + // [data-composition-id], so without this fill those pixels stay + // transparent — and the jpeg encode below turns them black. + // Resolve the nearest non-transparent ancestor background-color and + // paint it first, matching what captureScreenshot composites. + // (Resolved per frame: compositions may set body background from JS.) + let bg = ""; + for (let el = root.parentElement; el; el = el.parentElement) { + const c = getComputedStyle(el).backgroundColor; + if (c && c !== "transparent" && c !== "rgba(0, 0, 0, 0)") { + bg = c; + break; + } + } + if (bg) { + ctx.fillStyle = bg; + ctx.fillRect(0, 0, w, h); + } ( ctx as unknown as { drawElementImage(el: Element, x: number, y: number): void } ).drawElementImage(root, 0, 0);