@@ -75,12 +75,11 @@ internal class CanvasController : ICanvasController
7575 private bool _realImageDisplayedForCurrentPhoto ;
7676 private bool _isMultiPageActive ;
7777
78- // W2D-owned: set inside the ZoomOutOnExit action, read in the AnimationCompleted handler .
78+ // W2D-owned: set inside the ZoomOutOnExit action, read in WaitForPanZoomAnimationAsync .
7979 private bool _isGoingToExit ;
8080
8181 // W2D-owned: true while ZoomAtPointPrecision ticks are arriving (right-click continuous zoom).
82- // Cleared 700 ms after the last tick — just after the 650 ms offscreen timer fires — so Draw
83- // skips the offscreen and uses source-bitmap quality during the zoom burst.
82+ // Treated as animating so Draw uses mip-based quality. Cleared 700 ms after the last tick.
8483 private bool _continuousZoomActive ;
8584 private CancellationTokenSource _continuousZoomCts ;
8685
@@ -109,7 +108,6 @@ public CanvasController(CanvasAnimatedControl d2dCanvas, IThumbnailController th
109108 _d2dCanvas . DispatcherQueue . TryEnqueue ( ( ) => OnOneToOneStateChanged ? . Invoke ( isOneToOne ) ) ;
110109 _canvasViewManager . ZoomChanged += RequestZoomUpdate ;
111110 _canvasViewManager . ViewChanged += RequestInvalidate ;
112- _canvasViewManager . AnimationCompleted += CanvasViewManager_AnimationCompleted ;
113111 }
114112
115113 public void Dispose ( )
@@ -207,8 +205,8 @@ private async Task HandleHqAnimatedDisplayItemAsync(int currentOperationId, Phot
207205 {
208206 // For animated images, first display the static first frame immediately for responsiveness.
209207 InstallRenderer (
210- new StaticImageRenderer ( _d2dCanvas , _canvasViewState , animDispItem . Bitmap ,
211- photo . SupportsTransparency , RequestInvalidate , createOffScreen : false ) ,
208+ new StaticImageRenderer ( _d2dCanvas , animDispItem . Bitmap ,
209+ photo . SupportsTransparency , RequestInvalidate , generateMipChain : false ) ,
212210 _imageSize , animDispItem . Rotation , ctx , forceThumbNailRedraw : true ) ;
213211
214212 // Asynchronously create the appropriate animator (GIF, WebP, APNG, or AVIF).
@@ -247,7 +245,7 @@ private async Task HandleHqAnimatedDisplayItemAsync(int currentOperationId, Phot
247245 private void HandleHqStaticDisplayItem ( Photo photo , HqDisplayItem hqDispItem , PhotoInstallContext ctx )
248246 {
249247 InstallRenderer (
250- new StaticImageRenderer ( _d2dCanvas , _canvasViewState , hqDispItem . Bitmap ,
248+ new StaticImageRenderer ( _d2dCanvas , hqDispItem . Bitmap ,
251249 photo . SupportsTransparency , RequestInvalidate ) ,
252250 _imageSize , hqDispItem . Rotation , ctx , forceThumbNailRedraw : true ) ;
253251 }
@@ -269,8 +267,8 @@ private void HandlePreviewDisplayItem(Photo photo, PreviewDisplayItem previewDis
269267 var correctedWidth = _imageSize . Height * previewAspectRatio ;
270268
271269 InstallRenderer (
272- new StaticImageRenderer ( _d2dCanvas , _canvasViewState , previewDispItem . Bitmap ,
273- photo . SupportsTransparency , RequestInvalidate , createOffScreen : false ) ,
270+ new StaticImageRenderer ( _d2dCanvas , previewDispItem . Bitmap ,
271+ photo . SupportsTransparency , RequestInvalidate , generateMipChain : false ) ,
274272 new Size ( correctedWidth , _imageSize . Height ) , previewDispItem . Rotation , ctx , forceThumbNailRedraw : true ) ;
275273 }
276274
@@ -328,12 +326,6 @@ private void InstallRenderer(IRenderer newRenderer, Size imageSize,
328326
329327 if ( forceThumbNailRedraw )
330328 _thumbNailController . CreateThumbnailRibbonOffScreen ( ) ; // self-marshals to the UI thread
331-
332- // Skip the offscreen timer when an animation is starting — AnimationCompleted will call
333- // TryRedrawOffScreen(false) at the correct final scale. Starting the timer here while
334- // Scale is near-zero (startup zoom) would bake a tiny offscreen mid-animation.
335- if ( ! _canvasViewManager . PanZoomAnimationOnGoing )
336- _currentRenderer . RestartOffScreenDrawTimer ( ) ;
337329 } ) ;
338330 }
339331
@@ -344,7 +336,6 @@ public void FitToScreen(bool animateChange)
344336 EnqueueW2dAction ( ( ) =>
345337 {
346338 if ( _currentRenderer == null ) return ;
347- if ( animateChange ) _currentRenderer . CancelOffScreenTimer ( ) ;
348339 _canvasViewManager . ZoomPanToFit ( animateChange , imageSize , canvasSize ) ;
349340 } ) ;
350341 }
@@ -355,7 +346,7 @@ public void ZoomToHundred()
355346 EnqueueW2dAction ( ( ) =>
356347 {
357348 if ( _currentRenderer == null ) return ;
358- _currentRenderer . CancelOffScreenTimer ( ) ;
349+
359350 _canvasViewManager . ZoomToHundred ( canvasSize ) ;
360351 } ) ;
361352 }
@@ -366,7 +357,7 @@ public void ZoomToHundred(Point anchor)
366357 EnqueueW2dAction ( ( ) =>
367358 {
368359 if ( _currentRenderer == null ) return ;
369- _currentRenderer . CancelOffScreenTimer ( ) ;
360+
370361 _canvasViewManager . ZoomToHundred ( canvasSize , anchor ) ;
371362 } ) ;
372363 }
@@ -387,7 +378,7 @@ public void ZoomByKeyboard(ZoomDirection zoomDirection)
387378 EnqueueW2dAction ( ( ) =>
388379 {
389380 if ( _currentRenderer == null ) return ;
390- _currentRenderer . CancelOffScreenTimer ( ) ;
381+
391382 _canvasViewManager . ZoomAtCenter ( zoomDirection , canvasSize ) ;
392383 } ) ;
393384 }
@@ -398,7 +389,7 @@ public void StepZoom(ZoomDirection zoomDirection, Point? zoomAnchor = null)
398389 EnqueueW2dAction ( ( ) =>
399390 {
400391 if ( _currentRenderer == null ) return ;
401- _currentRenderer . CancelOffScreenTimer ( ) ;
392+
402393 _canvasViewManager . StepZoom ( zoomDirection , canvasSize , zoomAnchor ) ;
403394 } ) ;
404395 }
@@ -408,7 +399,7 @@ public void ZoomAtPoint(ZoomDirection zoomDirection, Point zoomAnchor)
408399 EnqueueW2dAction ( ( ) =>
409400 {
410401 if ( _currentRenderer == null ) return ;
411- _currentRenderer . CancelOffScreenTimer ( ) ;
402+
412403 _canvasViewManager . ZoomAtPoint ( zoomDirection , zoomAnchor ) ;
413404 } ) ;
414405 }
@@ -420,13 +411,9 @@ public void ZoomAtPointPrecision(int delta, Point zoomAnchor)
420411 if ( _currentRenderer == null ) return ;
421412 _canvasViewManager . ZoomAtPointPrecision ( delta , zoomAnchor ) ;
422413
423- // No animation → no AnimationCompleted → must restart the offscreen timer manually
424- // so the offscreen is recreated 650ms after the last zoom tick.
425- _currentRenderer . RestartOffScreenDrawTimer ( ) ;
426-
427- // Mark the burst as active so D2dCanvas_Draw skips the offscreen and uses
428- // source-bitmap quality. Debounce the clear to 700ms (just after the 650ms
429- // offscreen timer) so Draw switches back to the fresh offscreen once zoom stops.
414+ // Mark the burst as active so the render path uses mip-based quality instead of
415+ // source-bitmap quality. Debounced 700ms after the last tick so Draw reverts to
416+ // non-animating mip selection once the zoom burst ends.
430417 _continuousZoomActive = true ;
431418 _continuousZoomCts ? . Cancel ( ) ;
432419 _continuousZoomCts ? . Dispose ( ) ;
@@ -488,11 +475,12 @@ private void D2dCanvas_CreateResources(CanvasAnimatedControl sender,
488475 Microsoft . Graphics . Canvas . UI . CanvasCreateResourcesEventArgs args )
489476 {
490477 // Device (re)created (first load or device loss). Drop the device-bound brush so it is
491- // rebuilt lazily, and ask the current renderer to rebuild its off-screen surface.
478+ // rebuilt lazily, and rebuild the mip chain with the current scaling quality.
479+ // Note: CanvasBitmaps held in the photo cache are NOT re-created here (accepted risk).
492480 // Runs on the W2D thread.
493481 _checkeredBrush ? . Dispose ( ) ;
494482 _checkeredBrush = null ;
495- _currentRenderer ? . TryRedrawOffScreen ( true ) ;
483+ _currentRenderer ? . HandleScalingMethodChange ( ) ;
496484 }
497485
498486 /// <summary>
@@ -554,16 +542,9 @@ private void D2dCanvas_SizeChanged(object sender, SizeChangedEventArgs args)
554542 {
555543 if ( _currentRenderer == null ) return ;
556544 _canvasViewManager . HandleSizeChange ( newSize , previousSize ) ;
557- _currentRenderer . RestartOffScreenDrawTimer ( ) ;
558545 } ) ;
559546 }
560547
561- private void CanvasViewManager_AnimationCompleted ( )
562- {
563- if ( ! _isGoingToExit )
564- _currentRenderer ? . TryRedrawOffScreen ( false ) ;
565- }
566-
567548 /// <summary>
568549 /// Returns a Task that completes when the next pan/zoom animation finishes (via the W2D-thread
569550 /// <see cref="CanvasViewManager.AnimationCompleted"/> event), or after <paramref name="timeoutMs"/>
@@ -613,7 +594,12 @@ public bool IsPressedOnImage(Point position)
613594
614595 public void HandleCheckeredBackgroundChange ( )
615596 {
616- EnqueueW2dAction ( ( ) => _currentRenderer ? . TryRedrawOffScreen ( true ) ) ;
597+ EnqueueW2dAction ( ( ) => { } ) ; // wake the canvas; Draw() reads the setting live
598+ }
599+
600+ public void HandleImageScalingQualityChange ( )
601+ {
602+ EnqueueW2dAction ( ( ) => _currentRenderer ? . HandleScalingMethodChange ( ) ) ;
617603 }
618604
619605 /// <summary>
0 commit comments