Databound WPF, Multipage Tiffs, Memory Streams and Performance - c#

I have a databound object within a WPF control that is 'previewing' a mutlipage tiff.
The object has a public PreviewImage, and CurrentPreviewPage.
It has a private PreviewPages which is a collection of MemoryStreams (each representing a page of the Tiff).
Upon the get of the PreviewImage (the first time) this code runs:
if (PreviewPages.Count == 0)
{
Image myImg = System.Drawing.Image.FromFile(_LocalFile);
for (int i = 0; i < (NumberOfPages); i++)
{
myImg.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
System.IO.MemoryStream ms = new System.IO.MemoryStream();
myImg.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
PreviewPages.Add(ms);
}
}
The previous code takes about 10 seconds to run for a 1100KB 17 page TIFF. There must be a better way of handling this.
Afterwards, this is called:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 1000;
//bi.UriSource = new Uri(fiTemp.FullName);
bi.StreamSource = new System.IO.MemoryStream(PreviewPages[CurrentPreviewPage - 1].ToArray());
bi.EndInit();
_PreviewImage = bi;
Now, after the initialization this code works fantastically (it can change pages as fast as you can drag a bound slider). Any help would be much appreciated.

Load your first page on the primary thread and then additional pages in the background - see BackGroundWorker. Only primary thread can access the UI. On the BackGroundWorker you need to decide if you are going to get the pages 2-x one at a time or all at once. The user cannot get to page 2 until you bring page 2 to the UI thread. I would implement cancel. If the user gives up you don't want to tie up CPU.

Related

Bringing large image into view at runtime

Here is the problem.
I have a view, that should display an image and some controls.
User add new images, changes some options and click "finish".
Images are large and very large (400-1500 MB Tiff)
User should see the preview of image, but it is ok if it loading for 10-15 sec or even more, he have a job for this time.
Image is binding through MVVM pattern like simple string (file will be always in local folder)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
Problem is that all is hangs when user try to add a file for loading time.
I understand that this case should be solved through multithreading - but have no idea how to implement this.
I tryed to update image from view in different thread like this:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
But anyway at point
ImagePreview.Source = t_source;
everything hangs up until image fully loaded.
Is there a way to load a preview in the background and show it without those terrible hangs?
The probably most simple way of asynchronously loading an image is via an asynchronous Binding. You would not have to deal with Threads or Tasks at all.
<Image Source="{Binding Image, IsAsync=True}"/>
A possible view model could look like shown below, where you must make sure that the Image property getter can be called from a background thread.
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
As you already mentioned, you are blocking the UI thread with the image load. You can use a WriteableBitmap class instance as the source for your Image. This will let you load the image on a background thread or async task. Here is a quick guide (not mine) on the issue.
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
Another option would be using priortybinding with the highest priorty to the full image and a lower priority to the faster-loading preview image. MS has documented priority binding here:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding

Inserting Image to FlowDocument

I'm working on a wpf application. I want to create a FlowDocument object and print it. As the creation step takes some seconds and freeze the UI, I move my code to new thread. The problem is that I need to set an Image in the FlowDocument and need to create Image UIElement, but UI Controls cannot be created in background threads !
I've also tried so many Dispather.Invoke() scenarios, but they catch exception about object owner thread.
I wonder is there any other methods to insert image into the FlowDocument? Or is it possible to create Image UIElement in background thread?
any suggestion would be appreciated.
P.S : Some Example Code =>
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
Image v = new Image() { Source = bitmapImage };
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));
Image v = ((App)Application.Current).Dispatcher.Invoke(new Func<Image>(() =>
{
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
return new Image() { Source = bitmapImage};
}));
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));
If you don't need to modify the BitmapImage, then you can freeze it and use it on the UI thread.
// Executing on non UI Thread
BitmapImage bitmapImage = SingletonSetting.GetInstance().Logo;
bitmapImage.Freeze(); // Has to be done on same thread it was created on - maybe freeze it in the Singleton instead?
Application.Current.Dispatcher.Invoke(() => {
// Executing on UI Thread
Image v = new Image() { Source = bitmapImage };
currnetrow.Cells.Add(new TableCell(new BlockUIContainer(v)));
});
After chatting with you, what you really needed to do was run your task in a STA thread, since you were making UI controls on it.
How to do that? See this answer:
Set ApartmentState on a Task

How to speed up adding images to ImageList?

So I'm trying to populate a large amount of icons to a listview. In order to avoid long wait times, I'm trying to get it to load the first 1000 results and then load more if the user presses a load more button.
Here's where I'm stuck. If I load all 10,000+ icons at once it takes me 37 sec. However, if I decide to add 500 more icons everytime the user clicks the button it takes me 40 sec which is worse than adding it all at once! The only difference in code is that I had to make this one line a delegate to avoid cross-threading issues. Is there a faster way to do this?
for (int i = lastLoadedIndex; i < lastLoadedIndex+500; i++)
{
string file = resultArr[i];
Invoke((MethodInvoker)delegate()
{
this.imageList1.Images.Add(Image.FromFile(file));
});
}
To speed up any bulk operation, consider using the bulk method if it is available.
For instance, the ImageCollection type has the AddRange method.
Try to use it:
int newCount = 500;
// Get a desired part of the `resultArr` array as a new array:
string[] tmp = new string[newCount];
Array.Copy(resultArr, lastLoadedIndex, tmp, 0, newCount);
// Load images:
Image[] images = Array.ConvertAll(tmp, file => Image.FromFile(file));
// Bulk add images to the ImageList:
Invoke((MethodInvoker)(() => imageList1.Images.AddRange(images)));
If won't help, please check which operation is slow: reading of images or appending to the ImageList.
EDIT #2: The expensive part of the function is loading the image from the file. Placing it before locking the mutex should allow for some parallelism worth the overhead of using the mutex. No, this method does not preserve order.
EDIT: Add images directly to image list rather than to a temporary collection.
public void LoadImagesFromFiles(string[] files)
{
Mutex imageListLock = new Mutex();
files.AsParallel().ForAll(file =>
{
var img = Image.FromFile(file);
imageListLock.WaitOne();
this.imageList1.Images.Add(img);
imageListLock.ReleaseMutex();
});
}

how to load thumbnails in listview as needed?

So I want to be able to populate 1000s of thumbnails based on a list of image filepaths. I tried using the following code but I realized that after the 200+ image, my program would throw me a "A first chance exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll".
private void PopulateThumbnails(List<string> queryResults)
{
this.playerListView.Items.Clear();
this.imageList1.Images.Clear();
ImageViewer imageViewer = new ImageViewer();
foreach (string file in queryResults)
{
try
this.imageList1.Images.Add(Image.FromFile(file));
catch
Console.WriteLine("This is not an image file");
}
this.ListView.View = View.LargeIcon;
this.imageList1.ImageSize = new Size(256, 144);
this.ListView.LargeImageList = this.imageList1;
for (int j = 0; j < this.imageList1.Images.Count; j++)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = j;
this.ListView.Items.Add(item);
}
}
I realize that I should probably only populate the thumbnails as needed but...
a) how do I know how many items are being loaded in the listview?
b) how do I detect scroll events in listview?
This is rather tricky.
You have two problems:
You need to know which Images you need to display
You need to make sure that you don't run out of memory or GDI resources
Unfortunately the ListView doesn't help with Events or Properties that would notify you about scrolling or tell you which items are visible.
Here is a little piece of code that will do at least the latter; it load a few thousand filenames into a list images and keep a list of 100 items, last visible:
List<string> images = new List<string>();
List<int> visibleItems = new List<int>();
void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
// this loads the images on demand:
if (!imageList1.Images.ContainsKey(images[e.ItemIndex]) )
{ loadImage(e.ItemIndex); e.Item.ImageIndex = imageList1.Images.Count - 1; }
// this makes sure the listView is drawn by the system
e.DrawDefault = true;
// these lines would maintain a list of items that are or were recently visible
if (!visibleItems.Contains(e.ItemIndex)) visibleItems.Add(e.ItemIndex);
if (visibleItems.Count > 1000)
{
visibleItems.Clear();
imageList1.Images.Clear();
listView1.Invalidate();
}
}
void loadImage(int index)
{
imageList1.Images.Add(images[index], new Bitmap(images[index]) );
}
You need to set the ListView.OwnerDraw = true;; then the DrawItem event will be called and after setting e.DrawDefault = true; the actual drawing will be done by the system after all.
But the event will keep telling you exactly which Items are visible.
Now you can make sure that the respective Bitmaps are loaded in your ImageList or those far away are disposed again.. so far I have only implemented a load on demand only..
Update: Like Plutonix, I have runa few test and can load 3000 Images with 128x128 pixels each with out any problems at all. I load them on demand and it seems ImageList takes care of things quite nicely: No need to dispose of the Images, GDI resources stay at a low level (<100) for my thest program. The memory foorprint keep rising while I scroll but doesn't look as if it'll be a problem. It it is I can simply do a
imageList1.Images.Clear();
..and memory drops back to the starting point. Display works fine, reloading of the list starts without a glitch.. In fact I don't use the visibleItems list any longer. I keep it in the answer, as it is a feasible way to know about these Items but atm I see no need for it..

How can i make pictureboxe on the form more fastly on runtime in .net winforms?

I want to select the pictures (They are select during program is running) and show them on the form. for that i take a panel on the form and populate the panel with pictureboxes.i write the following code for that but it is very time consuming:
if(openDialoge1.ShowDialog() == DialogResult.OK)
{
string[] fileName = open.FileNames;
foreach (string s in fileName)
{
pBox = new PictureBox();
pBox.Size = new System.Drawing.Size(w, h);
pBox.Location = new System.Drawing.Point(x, y);
pBox.Image = Image.FromFile(s);
pBox.SizeMode = PictureBoxSizeMode.StretchImage;
.
.//here i add some eventHandler of picture boxes.
this.panel1.Controls.Add(pBox);
x += pBox.Width + 4;
}
} //here w,h,x,y are integers.
This code work well, but it is very time consuming and take much time to populate the panel with picture boxes. for example when i slect the 20,30 pictures, it take much time.
Is there any way that reduce the time to populate the panel with pictureboxes.
Thanks in advance.
You might consider profiling your method. If you don't have a profiler like ANTS available, you can roll your own:
Stopwatch watch = new Stopwatch();
watch.Start();
//code to profile goes here
watch.Stop();
Console.Writeln("Elapsed time: " + watch.Elapsed.TotalMilliseconds + "ms");
This will help you pinpoint which part of your code is slow.
I can tell you right now that Image.FromFile() is probably the slowest part. What you might consider is loading the images into a List<> first, using a separate thread or background worker. This will let you show a progress bar or hourglass, to let the user know that images are being read from disk.
Once the images are in memory, creating the picture boxes will go a lot faster.
Edit:
You've requested an example showing how to load images into memory first. It's really simple:
// this code should run in its own thread - BackgroundWorker is perfect for this
List<Image> images = new List<Image>();
foreach (string imagePath in paths)
{
images.Add(Image.FromFile(imagePath));
// update progress bar here?
}
Now, you have a list of your images which you can use to fill your picture boxes.

Categories

Resources