I was trying to generate thumbnails using Bitmap.GetThumbnailImage() function for 20+ images in a folder. I could see huge memory spike by the application when it does the following procedure (about 600,000K in Task Manager mem usage).
foreach (var image in ListOfImages)
{
var thumbnailFolder = #"\thumb";
var thumbnailFile = thumbnailFolder + "\\" + image.Name;
if (!Directory.Exists(thumbnailFolder))
{
Directory.CreateDirectory(thumbnailFolder);
}
if (!File.Exists(thumbnailFile))
{
using (FileStream fs = new FileStream(image.FullName, FileMode.Open, FileAccess.Read))
{
Image origImage = Image.FromStream(fs);
var thumbnail = origImage.GetThumbnailImage(90, 120, null, IntPtr.Zero);
thumbnail.Save(thumbnailFile);
thumbnail.Dispose();
origImage.Dispose();
}
}
}
Is there any way to reduce this much memory usage for thumbnail generation?
Give it a try using WPF.
In my experience WPF's image operations quite well optimized (actually it is the WIC library that's being used), and designed with threading in mind, and it does not depend the GDI bitmap handles like GDI+ does. I read once that GDI+ is not supported in server code because it is not entirely leakfree. For your scenario, WPF does not need a 3D video card.
WPF's BitmapDecoder even has built-in thumbnail functionality, which will take advantage of thumbnails in the image itself if available. See http://msdn.microsoft.com/en-us/library/ms750864(VS.85).aspx for basic image tasks in WPF. To access WPF, you need to reference the WindowsBase assembly (.net 3.0 or better).
Do not use Image.FromStream, use Image.FromFile instead for memory reasons. Frankly, I think you'd be better to adapt this example for quality reasons:
http://www.webcosmoforums.com/asp/321-create-high-quality-thumbnail-resize-image-dynamically-asp-net-c-code.html
Related
The problem seems to be already known with the handling of Image(s). I want to read a Image without locking it. Through various other questions (ex. question), I have found various workarounds. Something that works for many is to save the image using a bitmap ex:
if (new FileInfo(openImageDialog.FileName).Exists) {
Image tmp = Image.FromFile(openImageDialog.FileName);
pictureBoxImage.Image = new Bitmap(tmp);
tmp?.Dispose();
}
My problem with this is that I want to display a png with transparency, which is clearly lost with a bitmap.
Can someone come to my rescue?
Create your file stream explicitly and use Image.FromStream instead, allowing you to specify FileShare-mode:
using var fs = File.Open(openImageDialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
Image tmp = Image.FromStream(fs);
You could allow FileShare.ReadWrite, but that might not be a good idea since things will likely break if someone is concurrently writing to the same file.
I'm not sure what problem you are describing with regards to transparency. .Net Bitmaps support transparency just fine, .bmp files do not, but you can save and load png files using the Bitmap or Image classes. I'm also unsure what transparency has to do with file locking.
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?
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:
Here is some documentation and references that might be useful:
https://imagemagick.org/Usage/thumbnails/#creation
http://www.imagemagick.org/Usage/thumbnails/
https://github.com/dlemstra/Magick.NET
https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
https://devblogs.microsoft.com/dotnet/net-core-image-processing/
https://weblogs.asp.net/bleroy/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi
Alternatives to System.Drawing for use with ASP.NET?
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.
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.
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.
In order to learn how develop on C# and Visual Studio i made an offline UWP application to read Comic/Manga stored in my Windows-based tablet.
One of the main steps was take the relative directory of each image in one episode and create a BitmapImage of eachone to load them to the FLipView.
Currently i'm doing it this way:
foreach (String value in ImageDirectory)
{
StorageFile file = await StorageFile.GetFileFromPathAsync((value));
IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
image = new BitmapImage();
await image.SetSourceAsync(fileStream);
images.Add(image); //images is a <List> of BitmapImage
}
In the most extreme case i have, with 124 images (50,1 Mb on disc) when loaded they use about 860 Mb of Ram which seems excesive.
I know that load all the images as one is not the most efficent approach, where a more on-demad solution would be better, but finally my questions is:
Is there a better (that uses less ram) way to load the images?
You have to have in mind that images loaded in memory are not compressed like on your disk. They are handled as bitmaps (uncompressed)
if you want to see a preview from all images on your disk, a good approach is to resize your images in memory, like in How to Copy and Resize Image in Windows 10 UWP, and load the full image on demand.
I have used the sample code as found in the answer at how to create an animated gif in .net
ie
System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in images)
{
var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmpImage.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
gEnc.Frames.Add(BitmapFrame.Create(src));
}
using(FileStream fs = new FileStream(path, FileMode.Create))
{
gEnc.Save(fs);
}
I would now like to set the speed of the animate gif. The help in this area is terrible. Anyone got any idea of how to do it? This must surely be possible. It seems to be such a basic function of an animated gif but they seem to have gone out of their way to make it difficult.
Thanks in advance.
I don't think you can do it with GifBitmapEncoder. Theoretically, you could use BitmapMetadata.SetQuery to set the value of /grctlext/Delay on each frame (the delay is specified in the Graphic Control Extension of each frame, as explained in the Wikipedia article). But the doc says:
Graphics Interchange Format (GIF) images do not support global
preview, global thumbnails, global metadata, frame level thumbnails,
or frame level metadata.
Which is technically incorrect, because the GIF format itself does support global and frame-level metadata; it's just the GifBitmapEncoder class that doesn't support it.
So I think your only options are to use another, more complete image manipulation library, or to do it manually. The GIF format is rather simple; it's described in details on this site, including the parts about animation and LZW compression.
Alternatively, instead of doing the whole encoding yourself, you could use GifBitmapEncoder to do the heavy lifting, and just patch the metadata in the resuting stream afterwards. The code in my XamlAnimatedGif library might help; you won't be able to use it directly, but you could reuse some parts of it, as it implements a full GIF decoder.
I want to export an image of my ArcGIS map object with the graphics layer on it. I've tried esri's own web services for export but they're not so efficient and clear, not supporting complex geometric shapes also they're not support local layers such as Google map provider. Service supports only ArcGISTiledLayer i want it in all layers. So, i searched in their forums but they say they won't support local layers until next versions.
I've tried ImageTool libraries and WritableBitmapEx libraries in codeplex. But when i try to get byte[] from a WritableBitmap i can not access its Pixels property for some security reasons all the time. Application throws a SecurityException and says that 'you can't access this pixels property'.
So, is there any way for get a UIElement control's image and save it to the disk? Or is there a workaround for this security exception?
Yes the image tools library has a method to do this into png/jpg etc.
http://imagetools.codeplex.com/
Also you can use RenderTargetBitmap - http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap.aspx
Here is an example of how to save a file to disk. you can only do it from a dialog
http://www.silverlightshow.net/items/Using-the-SaveFileDialog-in-Silverlight-3.aspx
EDIT - Sample Code
Calling
var objImage = new WritableBitmap(MyElement, MyElement.RenderTransform);
var bytData = objImage.ToPng();
Extension Method
using ImageTools.IO.Png;
using ImageTools;
public static byte[] ToPng(this WriteableBitmap Image)
{
byte[] bytResult;
using (MemoryStream objPngStream = new MemoryStream())
{
PngEncoder objPngEncoder = new PngEncoder();
objPngEncoder.Encode(Image.ToImage(), objPngStream);
objPngStream.Seek(0, SeekOrigin.Begin);
bytResult = objPngStream.ToArray();
objPngStream.Close();
}
return bytResult;
}