@@ -397,6 +397,12 @@ static ImageDifferenceControl()
397397 AffectsRender < ImageDifferenceControl > ( AlphaProperty ) ;
398398 }
399399
400+ // Cached render target for performance optimization
401+ private RenderTargetBitmap _cachedRenderTarget ;
402+ private Size _cachedSize ;
403+ private Bitmap _cachedOldImage ;
404+ private Bitmap _cachedNewImage ;
405+
400406 public override void Render ( DrawingContext context )
401407 {
402408 base . Render ( context ) ;
@@ -409,19 +415,45 @@ public override void Render(DrawingContext context)
409415
410416 if ( drawLeft && drawRight )
411417 {
412- using ( var rt = new RenderTargetBitmap ( new PixelSize ( ( int ) Bounds . Width , ( int ) Bounds . Height ) , right . Dpi ) )
418+ // Bounds checking for safe integer casting
419+ var width = Math . Min ( Math . Max ( 1 , Bounds . Width ) , 8192 ) ; // Max 8K width
420+ var height = Math . Min ( Math . Max ( 1 , Bounds . Height ) , 8192 ) ; // Max 8K height
421+ var pixelWidth = ( int ) Math . Ceiling ( width ) ;
422+ var pixelHeight = ( int ) Math . Ceiling ( height ) ;
423+
424+ // Check if we need to recreate the cached render target
425+ var currentSize = new Size ( pixelWidth , pixelHeight ) ;
426+ var needsRecreate = _cachedRenderTarget == null ||
427+ _cachedSize != currentSize ||
428+ _cachedOldImage != left ||
429+ _cachedNewImage != right ;
430+
431+ if ( needsRecreate )
413432 {
414- using ( var dc = rt . CreateDrawingContext ( ) )
415- {
416- using ( dc . PushRenderOptions ( RO_SRC ) )
417- RenderSingleSide ( dc , left , rt . Size . Width , rt . Size . Height , Math . Min ( 1.0 , 2.0 - 2.0 * alpha ) ) ;
433+ // Dispose old cached render target
434+ _cachedRenderTarget ? . Dispose ( ) ;
435+
436+ // Create new render target with bounds checking
437+ _cachedRenderTarget = new RenderTargetBitmap ( new PixelSize ( pixelWidth , pixelHeight ) , right . Dpi ) ;
438+ _cachedSize = currentSize ;
439+ _cachedOldImage = left ;
440+ _cachedNewImage = right ;
441+ }
418442
419- using ( dc . PushRenderOptions ( RO_DST ) )
420- RenderSingleSide ( dc , right , rt . Size . Width , rt . Size . Height , Math . Min ( 1.0 , 2.0 * alpha ) ) ;
421- }
443+ // Render to the cached target
444+ using ( var dc = _cachedRenderTarget . CreateDrawingContext ( ) )
445+ {
446+ // Clear the render target first
447+ dc . DrawRectangle ( Brushes . Transparent , null , new Rect ( 0 , 0 , pixelWidth , pixelHeight ) ) ;
422448
423- context . DrawImage ( rt , new Rect ( 0 , 0 , Bounds . Width , Bounds . Height ) ) ;
449+ using ( dc . PushRenderOptions ( RO_SRC ) )
450+ RenderSingleSide ( dc , left , pixelWidth , pixelHeight , Math . Min ( 1.0 , 2.0 - 2.0 * alpha ) ) ;
451+
452+ using ( dc . PushRenderOptions ( RO_DST ) )
453+ RenderSingleSide ( dc , right , pixelWidth , pixelHeight , Math . Min ( 1.0 , 2.0 * alpha ) ) ;
424454 }
455+
456+ context . DrawImage ( _cachedRenderTarget , new Rect ( 0 , 0 , Bounds . Width , Bounds . Height ) ) ;
425457 }
426458 else if ( drawLeft )
427459 {
@@ -475,5 +507,30 @@ private void RenderSingleSide(DrawingContext context, Bitmap img, double w, doub
475507
476508 private static readonly RenderOptions RO_SRC = new RenderOptions ( ) { BitmapBlendingMode = BitmapBlendingMode . Source , BitmapInterpolationMode = BitmapInterpolationMode . HighQuality } ;
477509 private static readonly RenderOptions RO_DST = new RenderOptions ( ) { BitmapBlendingMode = BitmapBlendingMode . Difference , BitmapInterpolationMode = BitmapInterpolationMode . HighQuality } ;
510+
511+ protected override void OnUnloaded ( Avalonia . Interactivity . RoutedEventArgs e )
512+ {
513+ base . OnUnloaded ( e ) ;
514+
515+ // Clean up cached render target to prevent memory leaks
516+ _cachedRenderTarget ? . Dispose ( ) ;
517+ _cachedRenderTarget = null ;
518+ _cachedOldImage = null ;
519+ _cachedNewImage = null ;
520+ }
521+
522+ protected override void OnPropertyChanged ( AvaloniaPropertyChangedEventArgs change )
523+ {
524+ base . OnPropertyChanged ( change ) ;
525+
526+ // Invalidate cache when images change
527+ if ( change . Property == OldImageProperty || change . Property == NewImageProperty )
528+ {
529+ _cachedRenderTarget ? . Dispose ( ) ;
530+ _cachedRenderTarget = null ;
531+ _cachedOldImage = null ;
532+ _cachedNewImage = null ;
533+ }
534+ }
478535 }
479536}
0 commit comments