From e954fdabff21aa72cc2d474f11bbd1eaed0546c5 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Fri, 13 Mar 2026 21:09:43 +0300 Subject: [PATCH] Treat FOREGROUND_BLUR as layer blur wrapping children MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma's kiwi format uses FOREGROUND_BLUR for what the Plugin API calls LAYER_BLUR — a blur that applies to the entire layer including children. Previously FOREGROUND_BLUR was handled in the per-effect 'front' pass via applyClippedBlur, which only blurred the node's own fills (drawn before children). For nodes with no fills but with children (like groups), the blur had no visible effect. Now FOREGROUND_BLUR is handled alongside LAYER_BLUR in renderNode, using a saveLayer that wraps both the node content and its children. This fixes gradient blobs that should appear soft/blurred (e.g. the decorative gradients in gold-preview.fig's Preview Thumbnail). --- packages/core/src/renderer/scene.ts | 9 ++++----- tests/engine/renderer-effects.test.ts | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/core/src/renderer/scene.ts b/packages/core/src/renderer/scene.ts index 64ff0e51f..cde1a56e8 100644 --- a/packages/core/src/renderer/scene.ts +++ b/packages/core/src/renderer/scene.ts @@ -141,7 +141,9 @@ export function renderNode( canvas.saveLayer(r.opacityPaint) } - const layerBlur = node.effects.find((e) => e.visible && e.type === 'LAYER_BLUR') + const layerBlur = node.effects.find( + (e) => e.visible && (e.type === 'LAYER_BLUR' || e.type === 'FOREGROUND_BLUR') + ) if (layerBlur) { r.effectLayerPaint.setImageFilter(r.getCachedBlur(layerBlur.radius / 2)) canvas.saveLayer(r.effectLayerPaint) @@ -443,10 +445,7 @@ export function renderEffects( } } - if ( - (pass === 'behind' && effect.type === 'BACKGROUND_BLUR') || - (pass === 'front' && effect.type === 'FOREGROUND_BLUR') - ) { + if (pass === 'behind' && effect.type === 'BACKGROUND_BLUR') { r.applyClippedBlur(canvas, node, rect, hasRadius, effect.radius / 2) } diff --git a/tests/engine/renderer-effects.test.ts b/tests/engine/renderer-effects.test.ts index fb10c65f6..ae910d3ff 100644 --- a/tests/engine/renderer-effects.test.ts +++ b/tests/engine/renderer-effects.test.ts @@ -44,8 +44,8 @@ describe('Renderer handles all effect types', () => { expect(rendererSource).toContain("effect.type === 'BACKGROUND_BLUR'") }) - test('handles FOREGROUND_BLUR', () => { - expect(rendererSource).toContain("effect.type === 'FOREGROUND_BLUR'") + test('handles FOREGROUND_BLUR as layer blur', () => { + expect(rendererSource).toContain("e.type === 'FOREGROUND_BLUR'") }) }) @@ -107,9 +107,8 @@ describe('Blur effects use saveLayer pattern', () => { expect(layerBlurSection).toContain('saveLayer') }) - test('background and foreground blur use applyClippedBlur', () => { + test('background blur uses applyClippedBlur', () => { expect(rendererSource).toContain("effect.type === 'BACKGROUND_BLUR'") - expect(rendererSource).toContain("effect.type === 'FOREGROUND_BLUR'") expect(rendererSource).toContain('applyClippedBlur') })