Displaying Thumbnails of very high resolution images Fast with Minimal Delay
Asked Answered
B

4

9

I need to show the preview thumbnails of high resolution images in a control for user selection. I currently use ImageListView to load images. This works fine for low to medium resolution images.But when it comes to showing thumbnails of very high resolution images there is a noticeable delay.Sample image can be downloaded from https://drive.google.com/open?id=1Qgu_aVXBiMlbHluJFU4fBvmFC45-E81C

The image size is around 5000x3000 pixels and size is around 12 MB.The issue can be replicated by using 1000 copies of this image.

The issue screen capture is uploaded here

https://giphy.com/gifs/ZEH3T3JTfN42OL3J1A

The images are loaded using a background worker

foreach (var f in filepaths)
{
    imageListView1.Items.Add(f);              
}

1. In order to solve this issue I tried resizing large resolution images and adding the resized image to ImageListView ... but for resizing there is a heavy time consumption and thumbnail generation is slow.

Bitmap x = UpdatedResizeImage2(new Bitmap(f), new Size(1000, 1000));
string q = Path.GetTempPath() + Path.GetFileName(f);
x.Save(Path.GetTempPath() + Path.GetFileName(f));
x.Dispose();
imageListView1.Items.Add(Path.GetTempPath() + Path.GetFileName(f));

2. I have also tried Image.CreateThumbnail Method but this is also quite slow.

Is there a better way to solve this issue?

Broeder answered 10/9, 2019 at 16:36 Comment(10)
First of all, 1000x1000 is not a thumbnail size. I would go with much smaller such as 64x64 or even 128x128 pixels. Next, you need to preload the thumbnails before your user starts scrolling. Also, you can expect there to be some lag on so many images in the list view, the same behaviour can even be seen with Google Drive when you're scrolling through a folder full of photos, they are slow to load and there is some latencyCero
@Cero I have tried using smaller sizes too...Some lag is acceptable.. but if you see the screen capture you can see that there is too much lag and the thumbnails are not getting generated in a linear fashion.Broeder
Your thumbnails are 1MB each (1000 x 1000). You need to make them much smaller, and maybe not Bitmap try using JPG or PNG to make them even smaller.Cero
@Cero The resizing process is the one that takes time.. not image population..Broeder
Have you considered generating and storing the thumbnails ahead of time?Stroup
You could use WPF interop (learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/…) and use the DecodePixelWidth/Height properties. They use underlying Windows imaging layer technology ("WIC") to create an optimized thumbnail, saving lots of memory (and possibly cpu): learn.microsoft.com/en-us/dotnet/framework/wpf/… otherwise there's no magic bullet, bigger images take more time.Lucubrate
@SimonMourier Thanks a lot :) WIC seems to solve the problem.The control itself advertises to use WIC but it takes a lot of time to refresh when scrolling through.There is an option to manually set the initial thumbnail image,im using WIC to cache the thumbnail to disk and then set it manually.This seems to have solved the issue by speeding things up.And yes, there will be a delay which is acceptable.Broeder
@SimonMourier Have you seen the answer about using ImageMagick? Could you please take a look.Is it a better approach than WIC?Broeder
I'm not sure I understand the statement "Magick.NET is better library then WIC or many others libraries, for its reach features and good reputation" Where does that come from? I've tested the MagickImage code with your Arnold sample image on my 16-cores PC: 3 sec. My code: 0.12 sec.Lucubrate
also if 5000x3000 is the original, and they only have a view port of x by y and this is much less than the original you could basically make the hi rez a much lower rez of the original, unless they specifically say download and view 4k + image, a bit silly if they only have HD monitor.Horror
L
5

You could use WPF interop and use the DecodePixelWidth/Height properties. They use underlying Windows imaging layer technology ("Windows Imaging Component") to create an optimized thumbnail, saving lots of memory (and possibly CPU): How to: Use a BitmapImage (XAML)

You can also use WPF/WIC by code, with a code like this (adapted from this article The fastest way to resize images from ASP.NET. And it’s (more) supported-ish.. You just need to add a reference to PresentationCore and WindowsBase which shouldn't be an issue for a desktop app.

    // needs System.Windows.Media & System.Windows.Media.Imaging (PresentationCore & WindowsBase)
    public static void SaveThumbnail(string absoluteFilePath, int thumbnailSize)
    {
        if (absoluteFilePath == null)
            throw new ArgumentNullException(absoluteFilePath);

        var bitmap = BitmapDecoder.Create(new Uri(absoluteFilePath), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];
        int width;
        int height;
        if (bitmap.Width > bitmap.Height)
        {
            width = thumbnailSize;
            height = (int)(bitmap.Height * thumbnailSize / bitmap.Width);
        }
        else
        {
            width = (int)(bitmap.Width * thumbnailSize / bitmap.Height);
            height = thumbnailSize;
        }

        var resized = BitmapFrame.Create(new TransformedBitmap(bitmap, new ScaleTransform(width / bitmap.Width * 96 / bitmap.DpiX, height / bitmap.Height * 96 / bitmap.DpiY, 0, 0)));
        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(resized);
        var thumbnailFilePath = Path.ChangeExtension(absoluteFilePath, thumbnailSize + Path.GetExtension(absoluteFilePath));
        using (var stream = File.OpenWrite(thumbnailFilePath))
        {
            encoder.Save(stream);
        }
    }

Otherwise there are lots of tools out there like MagicScaler, FreeImage ImageSharp, ImageMagick, Imazen, etc. Most were written for ASP.NET/Web server scenarios (for which WPF is officially not supported but works, read the article) and are also cross-platform which you don't seem to need. I'm not sure they're generally faster or use less memory than builtin Windows technology, but you should test all this in your context.

PS: otherwise there's no magic bullet, bigger images take more time.

Lucubrate answered 18/9, 2019 at 7:30 Comment(10)
I'm sorry about the Bounty,It was auto awarded..There is no option to change and award you the same.I have accepted and upvoted your answer.Broeder
@Broeder - thanks. no worries, I think the (half of the) bounty goes to the most up-voted answer.Lucubrate
Thanks... Could you please take a look at this related question #58062404Broeder
I think your answer needs to be slightly altered to fix the issue of "File is being used elswhere" on creation of the stream.What do you think of Stream stream = new FileStream(fileToRead, FileMode.Creat, FileAccess.ReadWrite, FileShare.ReadWrite);Broeder
@Broeder - Tweaking FileStream is a good start, but I can recommend you my answer here: https://mcmap.net/q/176664/-wait-until-file-is-unlocked-in-net :-)Lucubrate
The file is used by another process issue breaks this solution .. please see #58130470Broeder
@Broeder - use my WrapSharingViolations utility.Lucubrate
Doesn't that cause the process to wait for release? What exactly is locking the file? I have not created an instance of it anywhere else...Broeder
Yes it waits but in the general case, there's no other solution. You should give it a try. Otherwise, use "procexp" tool (from sysinternals) "find handle or dll". If it finds your process, it means the way your program is made locks the file. That can happen when all things are asynchronous / on demand.Lucubrate
Thanks .. the issue wan undisposed Bitmap.Disposing it solved the issue.Broeder
E
10

I would suggest using image processing library such ImageMagick.

ImageMagick has optimized this feature and you have Magick.NET a nuget package for .NET.

It is simple and straight forward:

var file = new FileInfo(@"c:\temp\input.jpg");

using (MagickImage image = new MagickImage(file))
{
    {
        image.Thumbnail(new MagickGeometry(100, 100));
        image.Write(@"C:\temp\thumbnail.jpg");
    }
}

example I made:

enter image description here

Here is some documentation and references that might be useful:

East answered 16/9, 2019 at 4:24 Comment(4)
Thanks for your answer.WIC seems to work well .. have you seen the comment by @simon-mourier .Is ImageMagick more optimized ?Broeder
is it better than WIC to be specific ?Broeder
Could you please mention that in the answer.Broeder
I will try this and get back to you.ThanksBroeder
L
5

You could use WPF interop and use the DecodePixelWidth/Height properties. They use underlying Windows imaging layer technology ("Windows Imaging Component") to create an optimized thumbnail, saving lots of memory (and possibly CPU): How to: Use a BitmapImage (XAML)

You can also use WPF/WIC by code, with a code like this (adapted from this article The fastest way to resize images from ASP.NET. And it’s (more) supported-ish.. You just need to add a reference to PresentationCore and WindowsBase which shouldn't be an issue for a desktop app.

    // needs System.Windows.Media & System.Windows.Media.Imaging (PresentationCore & WindowsBase)
    public static void SaveThumbnail(string absoluteFilePath, int thumbnailSize)
    {
        if (absoluteFilePath == null)
            throw new ArgumentNullException(absoluteFilePath);

        var bitmap = BitmapDecoder.Create(new Uri(absoluteFilePath), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];
        int width;
        int height;
        if (bitmap.Width > bitmap.Height)
        {
            width = thumbnailSize;
            height = (int)(bitmap.Height * thumbnailSize / bitmap.Width);
        }
        else
        {
            width = (int)(bitmap.Width * thumbnailSize / bitmap.Height);
            height = thumbnailSize;
        }

        var resized = BitmapFrame.Create(new TransformedBitmap(bitmap, new ScaleTransform(width / bitmap.Width * 96 / bitmap.DpiX, height / bitmap.Height * 96 / bitmap.DpiY, 0, 0)));
        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(resized);
        var thumbnailFilePath = Path.ChangeExtension(absoluteFilePath, thumbnailSize + Path.GetExtension(absoluteFilePath));
        using (var stream = File.OpenWrite(thumbnailFilePath))
        {
            encoder.Save(stream);
        }
    }

Otherwise there are lots of tools out there like MagicScaler, FreeImage ImageSharp, ImageMagick, Imazen, etc. Most were written for ASP.NET/Web server scenarios (for which WPF is officially not supported but works, read the article) and are also cross-platform which you don't seem to need. I'm not sure they're generally faster or use less memory than builtin Windows technology, but you should test all this in your context.

PS: otherwise there's no magic bullet, bigger images take more time.

Lucubrate answered 18/9, 2019 at 7:30 Comment(10)
I'm sorry about the Bounty,It was auto awarded..There is no option to change and award you the same.I have accepted and upvoted your answer.Broeder
@Broeder - thanks. no worries, I think the (half of the) bounty goes to the most up-voted answer.Lucubrate
Thanks... Could you please take a look at this related question #58062404Broeder
I think your answer needs to be slightly altered to fix the issue of "File is being used elswhere" on creation of the stream.What do you think of Stream stream = new FileStream(fileToRead, FileMode.Creat, FileAccess.ReadWrite, FileShare.ReadWrite);Broeder
@Broeder - Tweaking FileStream is a good start, but I can recommend you my answer here: https://mcmap.net/q/176664/-wait-until-file-is-unlocked-in-net :-)Lucubrate
The file is used by another process issue breaks this solution .. please see #58130470Broeder
@Broeder - use my WrapSharingViolations utility.Lucubrate
Doesn't that cause the process to wait for release? What exactly is locking the file? I have not created an instance of it anywhere else...Broeder
Yes it waits but in the general case, there's no other solution. You should give it a try. Otherwise, use "procexp" tool (from sysinternals) "find handle or dll". If it finds your process, it means the way your program is made locks the file. That can happen when all things are asynchronous / on demand.Lucubrate
Thanks .. the issue wan undisposed Bitmap.Disposing it solved the issue.Broeder
E
5

There's also NetVips, the C# binding for libvips.

It's quite a bit quicker than Magick.NET: between 3x and 10x faster, depending on the benchmark.

Thumbnailing is straightforward:

using NetVips;

var image = Image.Thumbnail("some-image.jpg", 128);
image.WriteToFile("x.jpg");

There's an introduction in the documentation.

Enure answered 7/11, 2019 at 11:8 Comment(2)
WriteToFile is slowest. Takes 60ms to generate thumbnail and 5048ms for writing.Weidner
I think you've maybe misunderstood the benchmark. Net-vips is demand-driven, so all work happens in the final write operation. This means you need to compare the writetofile time to the whole of load/resize/save added together in other systems, in other worlds, look at the whole end to end time, not the time of operations within it. Here's a benchmark against a wider range of C# image processing libs: github.com/bleroy/core-imaging-playground/pull/24 expand the results section, and ignore the first part.Enure
W
0

Most of answers approach is to resize bitmap and then save it. Its a bit offcourse slow, specially if you say very high resolution.

Why not use existing thumbnail created by windows explorer ? This is fastest way of all (specially if you use smaller thumbnails).

//https://stackoverflow.com/a/1751610
using Microsoft.WindowsAPICodePack.Shell;

var shellFile = ShellFile.FromFilePath(pathToYourFile); Bitmap
Image image = shellFile.Thumbnail.LargeBitmap;

Nuget : https://www.nuget.org/packages/WindowsAPICodePack-Shell (around 600KB)

Note: Its same as others, if thumbnail arent cached already.

Weidner answered 21/10, 2022 at 12:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.