-
-
Notifications
You must be signed in to change notification settings - Fork 456
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Checks
-
I have checked that this issue has not already been reported.
-
I am using the latest version of Flow Launcher.
-
I am using the prerelease version of Flow Launcher.
Problem Description
Diagnosis details
- Code path:
Flow.Launcher.Infrastructure.Image.ImageLoader- When
loadFullImage == falseand the file is an image extension handled by the thumbnail path, it invokes:image = GetThumbnail(path, ThumbnailOptions.ThumbnailOnly);
- When
GetThumbnailcallsWindowsThumbnailProvider.GetThumbnail(...), which uses the Shell to provide anHBITMAP.- Under certain conditions (e.g., extraction failure, missing or unreachable resource, or just a visually identical small-size render), the Shell returns the same or a generic icon, producing identical pixel data across files.
- If the hashing algorithm operates on non-normalized or encoded data (e.g., JPEG streams with
MemoryStream.GetBuffer()), collisions are more likely or non-deterministic. Even with raw pixel hashing, if the input pixels are identical, the hash will be identical.
CallStack if you will...
| public string GetHashFromImage(ImageSource imageSource) |
| public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options) |
| private static ImageResult GetThumbnailResult(ref string path, bool loadFullImage = false) |
| private static async ValueTask<ImageResult> LoadInternalAsync(string path, bool loadFullImage = false) |
Expected behavior
- Distinct image files should produce different hashes (or, if the project intends a visual hash, the policy should be documented and the pipeline should minimize accidental equivalence).
Actual behavior
- Different image files can produce identical hashes when the Shell returns identical thumbnails (e.g., generic icon or identical small-size render), causing cache key collisions and wrong icon reuse.
Suspected root cause
- Hashing relies on Shell-provided thumbnails in the small-icon path (
ThumbnailOnly), which can be identical for different files. - Additional instability may occur if the hashing uses encoded streams or non-normalized pixel data.
Proposed fix
- Compute hashes from normalized raw pixels (e.g., convert to
PixelFormats.Pbgra32, copy viaCopyPixels, hash with SHA-1/SHA-256) to remove encoder/stride artifacts. - Create
BitmapSourcewithBitmapSizeOptions.FromWidthAndHeight(width, height)andFreeze()for deterministic sizing/DPI. - Optional policy change (if file-identity is desired): For image files, hash a deterministic decode of the original image rather than the Shell thumbnail. Keep thumbnail hashing for non-image files.
Workarounds
- Use
loadFullImage == truefor image files when computing cache keys, or disable hash-based de-duplication for thumbnails.
Additional context
- Collisions observed with
upgrade.pngvsremove.pngat small icon size usingThumbnailOnly.
Rough patch that I tested and seemed to work.
public string GetHashFromImage(ImageSource imageSource)
{
if (imageSource is not BitmapSource image)
{
return null;
}
try
{
// Normalize pixel format to ensure consistent hashing regardless of source format/DPI
BitmapSource normalized = image;
if (image.Format != PixelFormats.Pbgra32)
{
var converted = new FormatConvertedBitmap();
converted.BeginInit();
converted.Source = image;
converted.DestinationFormat = PixelFormats.Pbgra32;
converted.EndInit();
converted.Freeze();
normalized = converted;
}
// Copy raw pixels. This avoids encoder differences (e.g., JPEG compression artifacts)
var width = normalized.PixelWidth;
var height = normalized.PixelHeight;
var bpp = normalized.Format.BitsPerPixel;
var stride = (width * bpp + 7) / 8; // WPF allows unaligned stride here
var pixels = new byte[stride * height];
normalized.CopyPixels(pixels, stride, 0);
using var sha1 = SHA1.Create();
var hashBytes = sha1.ComputeHash(pixels);
return Convert.ToBase64String(hashBytes);
}
catch
{
return null;
}To Reproduce
- Prepare two different image files (e.g.,
upgrade.pngandremove.png). - Ensure the small icon path is used (i.e.,
loadFullImage == false). - Generate thumbnails with:
var img1 = WindowsThumbnailProvider.GetThumbnail(path1, ImageLoader.SmallIconSize, ImageLoader.SmallIconSize, ThumbnailOptions.ThumbnailOnly); var img2 = WindowsThumbnailProvider.GetThumbnail(path2, ImageLoader.SmallIconSize, ImageLoader.SmallIconSize, ThumbnailOptions.ThumbnailOnly);
- Hash with
ImageHashGenerator.GetHashFromImage(...). - Compare the two hashes.
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working