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?

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
techno
  • 6,100
  • 16
  • 86
  • 192
  • 2
    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 latency – Jon Sep 10 '19 at 17:43
  • @Jon 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. – techno Sep 10 '19 at 17:47
  • 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. – Jon Sep 10 '19 at 17:47
  • @Jon The resizing process is the one that takes time.. not image population.. – techno Sep 10 '19 at 17:49
  • Have you considered generating and storing the thumbnails ahead of time? – David Browne - Microsoft Sep 14 '19 at 17:38
  • You could use WPF interop (https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-wpf-composite-control-in-windows-forms) 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): https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-use-a-bitmapimage otherwise there's no magic bullet, bigger images take more time. – Simon Mourier Sep 14 '19 at 21:07
  • @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. – techno Sep 15 '19 at 08:07
  • @SimonMourier Have you seen the answer about using ImageMagick? Could you please take a look.Is it a better approach than WIC? – techno Sep 18 '19 at 03:58
  • 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. – Simon Mourier Sep 18 '19 at 07:32
  • 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. – Seabizkit Sep 20 '19 at 14:27

4 Answers4

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:

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
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.

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • 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. – techno Sep 23 '19 at 11:09
  • @techno - thanks. no worries, I think the (half of the) bounty goes to the most up-voted answer. – Simon Mourier Sep 23 '19 at 11:32
  • Thanks... Could you please take a look at this related question https://stackoverflow.com/questions/58061904/using-wic-to-quickly-create-scaled-down-bitmap-that-honors-exif-orientation-tag – techno Sep 23 '19 at 11:55
  • 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);` – techno Sep 26 '19 at 18:39
  • @techno - Tweaking FileStream is a good start, but I can recommend you my answer here: https://stackoverflow.com/a/5441631/403671 :-) – Simon Mourier Sep 26 '19 at 18:51
  • The `file is used by another process` issue breaks this solution .. please see https://stackoverflow.com/questions/58129970/implementing-autofilename-incrementor-for-caching-thumbnails-using-dictionary – techno Sep 27 '19 at 07:51
  • @techno - use my WrapSharingViolations utility. – Simon Mourier Sep 27 '19 at 08:00
  • 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... – techno Sep 27 '19 at 08:05
  • 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. – Simon Mourier Sep 27 '19 at 08:33
  • Thanks .. the issue wan undisposed Bitmap.Disposing it solved the issue. – techno Sep 27 '19 at 09:52
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.

jcupitt
  • 10,213
  • 2
  • 23
  • 39
  • WriteToFile is [slowest](https://gist.github.com/kleisauke/879e3075a675c6f945dff3772d2ef0a3). Takes 60ms to generate thumbnail and 5048ms for writing. – Gray Programmerz Dec 10 '22 at 10:03
  • 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: https://github.com/bleroy/core-imaging-playground/pull/24 expand the results section, and ignore the first part. – jcupitt Dec 10 '22 at 13:13
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.

Gray Programmerz
  • 479
  • 1
  • 5
  • 22