Optimizing imaging in WPF application
08:37 07 Jun 2026

Briefly about the question:

How to reach same performance with images like explorer, FastStone Image Viewer since they allocate not so much memory unlike my app.

About the project

It's WPF C# image gallery app with their grouping feature. It has visual extension which probably affects performance in my case - WPF UI by Lepo. User can paste images from several sources: user's desk and paste images from clipboard. Images stored as .png file.

The problem

Application itself works fine for me, but it eats tons of memory. I've loaded 1,5k unique images taking ~1,6gb memory in total on the desk. But as for RAM in run-time, app consumes it up to 7gb. That's the case when i load all images.

What did i do

I've tried to reduce images thumbnail quality, which helped but not a lot. I've even tried to get into interop level and get images thumbnails natively. But this method works well only when explorer loads thumbnails before i run application making it unreliable to optimize it completely. I haven't seen any reliable method to optimize imaging in wpf excepting reducing images quality.

Project's code

I'll leave here essential parts of code associated with imaging in the app. I can create github repository if the question is lacking of information.

Object storing data about image. We create it and load it's thumbnail on initialization (Image property).

[JsonObject]
public class ImageItem
{
    [JsonProperty]
    public string Path { get; set; }
    [JsonProperty]
    public List Categories { get; set; }
    [JsonIgnore]
    public BitmapSource Image { get; set; }
    [JsonIgnore]
    public DateTime CreationTime => File.GetCreationTime(Path);
}

Images are stored in observable collections. I use Dynamic Data's SourceCache.

public SourceCache Images { get; } = new SourceCache(f => f.Path);

Next XAML code uses custom masonry panel. I can't show it since it's about 500 lines of code but it don't do anything to RAM. The code itself is just display of collection of images.


    
        
            
                
                    
                        
                        
                        
                        
                        
                    
                
            
        
    

    
        
            
        
    

That's method which loads thumbnail. Thumbnail quality width is 384 (c_lowQualityWidth field).

//Loads thumbnail
public static BitmapSource LoadOrGetImage(string path)
{
    //Ensure path is not empty
    ArgumentException.ThrowIfNullOrWhiteSpace(path);
    path = Path.GetFullPath(path);

    //If image exists we pick it from dictionary
    if (Images.ContainsKey(path))
    {
        return Images[path];
    }

    //Native thumbnail loading
    Guid iid = typeof(IShellItemImageFactory).GUID;

    int hr = NativeImageLoading.SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out IShellItemImageFactory factory);

    //Ensure method does return result and initializing factory
    if (hr != 0 || factory == null)
    {
        Marshal.ThrowExceptionForHR(hr);
    }

    //Thumbnail size
    SIZE size = new SIZE();
    size.cx = c_lowQualityWidth;
    size.cy = c_lowQualityWidth;

    //If successed it returns HBITMAP handle, otherwise it throws an exception which is catched and then we fallback to file decoding
    try
    {
        factory.GetImage(size, SIIGBF.SIIGBF_INCACHEONLY, out IntPtr hbitmap);

        BitmapSource source = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        source.Freeze();

        NativeImageLoading.DeleteObject(hbitmap);

        Images.TryAdd(path, source);

        return source;
    }
    catch
    {
        return DecodeImage(path);
    }
    //Utilizing object which is not managed by .NET, so we need to release it manually
    finally
    {
        Marshal.ReleaseComObject(factory);
    }
}

//Decode image from file and scale it down to thumbnail size
private static BitmapSource DecodeImage(string path)
{
    using var stream = File.OpenRead(path);
    var decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);

    BitmapFrame frame = decoder.Frames[0];

    double scale = c_lowQualityWidth / (double)frame.PixelWidth;
    int targetHeight = (int)(frame.PixelHeight * scale);

    TransformedBitmap image = new TransformedBitmap(frame, new ScaleTransform(scale, scale));
    image.Freeze();
    return image;
}

Probable cause

The issue probably comes from thumbnails are stored in RAM when loaded.

I'm also looking for extensions or existing solutions which can help me with optimizing it all.

c# wpf image data-binding wpf-controls