Skip to content

Fix PictureBox StretchImage repaint perf regression by caching scaled bitmap#14559

Open
LeafShi1 wants to merge 3 commits into
dotnet:mainfrom
LeafShi1:Fix_14009_PictureBox_StretchImage_cache
Open

Fix PictureBox StretchImage repaint perf regression by caching scaled bitmap#14559
LeafShi1 wants to merge 3 commits into
dotnet:mainfrom
LeafShi1:Fix_14009_PictureBox_StretchImage_cache

Conversation

@LeafShi1
Copy link
Copy Markdown
Member

@LeafShi1 LeafShi1 commented May 25, 2026

Fixes #14009

Proposed changes

  • Added a scaled bitmap cache for StretchImage (key: source image + destination size).
  • In OnPaint, when cache is hit, render via non-scaling path (DrawImageUnscaled).
  • Invalidate/rebuild cache on image change, SizeMode change, Resize, and Dispose.
  • Bypass cache for animated images (ImageAnimator.CanAnimate) to preserve frame update semantics.

Customer Impact

  • Lower CPU usage during frequent repaints with StretchImage

Regression?

  • Yes

Risk

  • Minimal

Screenshots

Before

On a page containing a PictureBox that uses StretchImage, pressing A to run 1000 refreshes takes about 11348ms.

image

After

The same 1000 refreshes complete much faster.

image

Test methodology

  • Manually and unit tests

Test environment(s)

  • .net 11.0.0-preview.5.26272.112
Microsoft Reviewers: Open in CodeFlow

…on-aware bitmap caching with memory cap and lazy invalidation, while preserving animation behavior and updating tests accordingly.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a reported performance regression in PictureBox when SizeMode=StretchImage by introducing a cache of the scaled bitmap to avoid repeated high-cost scaling during frequent repaints (e.g., resizing).

Changes:

  • Add a per-PictureBox scaled-bitmap cache keyed by destination size and Graphics.InterpolationMode, and render cached results via a non-scaling draw path.
  • Invalidate/dispose the cache on image changes, SizeMode changes, and control disposal; bypass caching for animated images and oversized targets.
  • Add unit tests validating cache reuse/recreation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/System.Windows.Forms/System/Windows/Forms/Controls/PictureBox/PictureBox.cs Adds scaled bitmap caching logic to optimize StretchImage painting.
src/test/unit/System.Windows.Forms/System/Windows/Forms/PictureBoxTests.cs Adds unit tests for the new StretchImage cache behavior (reuse, recreation, bypass).
Comments suppressed due to low confidence (2)

src/System.Windows.Forms/System/Windows/Forms/Controls/PictureBox/PictureBox.cs:1172

  • TryGetStretchImageCache returns false for non-cacheable scenarios (e.g., CanCacheStretchImage is false for large sizes, or ImageAnimator.CanAnimate is true) without clearing any previously created _stretchedImageCache. If a cache was built at a smaller size and the control later becomes non-cacheable (e.g., resized above the threshold), the old bitmap stays allocated indefinitely even though it will never be used. Consider disposing the existing cache when the method determines caching is not applicable for the current state.
        if (_sizeMode != PictureBoxSizeMode.StretchImage
            || _image is null
            || _imageInstallationType == ImageInstallationType.ErrorOrInitial
            || drawingRect.Width <= 0
            || drawingRect.Height <= 0
            || ImageAnimator.CanAnimate(_image)
            || !CanCacheStretchImage(drawingRect.Size))
        {
            return false;

src/System.Windows.Forms/System/Windows/Forms/Controls/PictureBox/PictureBox.cs:1196

  • CanCacheStretchImage uses (long)width * height * 4 to estimate allocation size. For extremely large dimensions, the multiplication can overflow Int64 (e.g., width≈height≈int.MaxValue), potentially making the expression appear below the threshold and leading to an attempt to allocate an impossibly large Bitmap. Consider using checked arithmetic or (ulong) multiplication with explicit overflow handling/clamping before comparing to MaxCachedStretchImageBytes.
    private static bool CanCacheStretchImage(Size drawingSize)
        => (long)drawingSize.Width * drawingSize.Height * sizeof(uint) <= MaxCachedStretchImageBytes;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/System.Windows.Forms/System/Windows/Forms/Controls/PictureBox/PictureBox.cs Outdated
…icit destination rectangle and GraphicsUnit.Pixel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PictureBox with stretched image in .Net 8 is 10 times slower than in .Net Framework

2 participants