From c3497a536b5192d786b61ba81b0e3ab088c6f7f9 Mon Sep 17 00:00:00 2001 From: Rajul Ranga Date: Mon, 20 Apr 2026 17:23:37 +0530 Subject: [PATCH] fix: improve video preview quality by deferring filter attachment The video preview was noticeably blurry/pixelated because PixiJS filters were always attached to the video container, even when inactive (blur=0, no motion). This forced every frame through an intermediate RenderTexture at renderer resolution, downsampling the native video before it even reached the screen. Now filters are only attached during active zoom transitions when the camera is actually moving, and detached as soon as it settles. Added hysteresis to the motion threshold to prevent filter toggling when intensity hovers near the boundary. --- src/components/video-editor/VideoPlayback.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 045d9304..af6d4685 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -1458,7 +1458,11 @@ const VideoPlayback = forwardRef( blurFilter.resolution = app.renderer.resolution; blurFilter.blur = 0; const motionBlurFilter = new MotionBlurFilter([0, 0], 5, 0); - videoContainer.filters = [blurFilter, motionBlurFilter]; + // Don't attach filters by default — the filter pipeline forces the video + // through an intermediate RenderTexture at renderer resolution, downsampling + // the native video and destroying detail. Filters are attached conditionally + // in the ticker only when zoom motion blur is actually active. + videoContainer.filters = null; blurFilterRef.current = blurFilter; motionBlurFilterRef.current = motionBlurFilter; @@ -1543,6 +1547,7 @@ const VideoPlayback = forwardRef( const appliedTransform = applyZoomTransform({ cameraContainer, blurFilter: blurFilterRef.current, + motionBlurFilter: motionBlurFilterRef.current, stageSize: stageSizeRef.current, baseMask: baseMaskRef.current, zoomScale: state.scale, @@ -1553,7 +1558,6 @@ const VideoPlayback = forwardRef( motionVector, isPlaying: isPlayingRef.current, motionBlurAmount: zoomMotionBlurRef.current, - motionBlurFilter: motionBlurFilterRef.current, transformOverride: transform, motionBlurState: motionBlurStateRef.current, frameTimeMs: performance.now(), @@ -1738,6 +1742,21 @@ const VideoPlayback = forwardRef( motionIntensity, motionVector, ); + + // Conditionally attach motion blur filter only when the camera is + // actually moving. When filters are attached, PixiJS routes the video + // through an intermediate RenderTexture at renderer resolution, which + // downsamples the native video and degrades preview quality. + // Hysteresis prevents flickering when motionIntensity oscillates near threshold. + const filtersActive = Array.isArray(videoContainer.filters) && videoContainer.filters.length > 0; + const cameraIsMoving = filtersActive ? motionIntensity > 0.002 : motionIntensity > 0.008; + const needsFilters = zoomMotionBlurRef.current > 0 && isPlayingRef.current && cameraIsMoving; + if (needsFilters && !filtersActive && motionBlurFilterRef.current) { + videoContainer.filters = [motionBlurFilterRef.current]; + } else if (!needsFilters && filtersActive) { + videoContainer.filters = null; + } + applyWebcamBubbleLayout(animationStateRef.current.appliedScale || 1); const timeMs = currentTimeRef.current;