[Peek] Fix thumbnails being created and not used. Fix icon bitmaps leaking memory. Simplify ImagePreviewer. #34544
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary of the Pull Request
This fixes various issues around thumbnail and icon creation. Most significantly,
ImagePreviewer
was always creating two thumbnails for every file navigated, which were almost always unused.The PR also consolidates two thumbnail creation classes, and fixes
IconHelper
failing to delete unmanaged bitmaps.PR Checklist
Detailed Description of the Pull Request / Additional comments
1. Thumbnail Creation
Two thumbnail/icon creation classes were used previously, providing similar functionality:
IconHelper
andThumbnailHelper
. These have now been combined into a newThumbnailHelper
, which includes appropriate comments for the class and public members. The oldThumbnailHelper
returned unmanaged bitmap handles, which were not deleted correctly, leading to a memory leak. The newThumbnailHelper
only returns managedImageSource
s.Previewers which used
IconHelper
have been updated to useThumbnailHelper
, which behaves identically for these calls.2. ImagePreviewer Refactor
ImagePreviewer had several issues:
ThumbnailHelper
, for the 2 thumbnail images, which meant it had to translate unmanaged bitmap handles intoImageSource
instances, and also mandated the deletion of the unmanaged resources when it was about to go out of scope. This also had the disadvantage of keeping the unmanaged resources around even after they had been translated intoImageSource
s, which was unnecessary.The updated ImagePreviewer is 50% smaller, only creates a thumbnail when needed (when the full-size image fails to load), and is fully managed, meaning it no longer needs to implement
IDisposable
. It uses significantly less memory: garbage collection is reduced by ~75% in testing.The thumbnail retrieval code now deliberately only checks the Explorer thumbnail/icon cache, as it can be assumed after the full-size image load failed that there is an issue with accessing the file. We may wish to implement retry logic in the future.
Validation Steps Performed
Testing was performed on three types of build:
FilePreview.xaml.cs
in these builds, to track the difference between old and new approaches forImagePreviewer
.Tests were performed on a folder containing approximately 90% image files of various resolutions, but mostly 1024x1024 JPEGs. The folder also included 'special' folders (i.e. those with icons in Explorer), zips, exe files, a PDF, an epub file, a supported RAW file (an RW2 sample) and 2 unsupported RAW files (DNG files). The non-image and unsupported files were included so I could confirm the other previewers were unaffected.
I attached to the running instance of PowerToys.Peek.UI.exe in Visual Studio before starting each run. Heap snapshots were taken at the start and then after previewing between 100 and 500 files. (I tried to wait until just after a GC, so this is not an exact count.) The snapshots and process memory used were compared, and the GC counts noted for runs where this was instrumented.
For thumbnail testing runs, special care was taken to clear the thumbnail and icon caches between runs. Just prior to starting each run, the test folder was opened again in Explorer to cache the thumbnails for a select number of files. This let me test the difference between cached and non-cached behaviour and to see whether the
PreviewState.Error
state was still handled correctly.Finally, to confirm that the thumbnail routine always retrieved the highest quality thumbnail available, I opened the Details Pane in Explorer and clicked on one or more files in the test folder. This served to prime the thumbnail cache with high resolution previews for those specific images. The resolution difference was noted in the subsequent Peek run by comparing with the other non-previewed images.
Example Run GC Stats