-1

I created a simple program that display image from selected item in Listbox1 to pictureBox1. It works fine but it takes about 200 milliseconds to display next image. My computer is using Intel i7 processor and Windows 7 64-bit. Please advise how I can speed up the process. Below are my codes. Thanks!

private void openToolStripMenuItem_Click(object sender, EventArgs e)
    {           
        folderBrowserDlg.SelectedPath = folderpath;
        this.folderBrowserDlg.ShowNewFolderButton = false; //Disable New Folder button
        DialogResult result = this.folderBrowserDlg.ShowDialog();
        if (result == DialogResult.OK)
        {

                folderpath = this.folderBrowserDlg.SelectedPath;
                string ImagePath = folderpath.Substring(0, folderpath.LastIndexOf(("\\")));
                folderName = folderpath.Substring(folderpath.LastIndexOf(("\\")) + 1);
                PathLength = ImagePath.Length; //Use for Substring later
                txtBrowse.Text = folderpath; //Get folder path and display to textbox


                var filearray = Directory.EnumerateFiles(folderpath, "*.*", SearchOption.AllDirectories).Where(a => a.EndsWith(".tif") || a.EndsWith(".tiff"));
                array = filearray.ToArray();                   

                var filenames = Directory.EnumerateFiles(folderpath, "*.*", SearchOption.AllDirectories).Where(a => a.EndsWith(".tif") || a.EndsWith(".tiff")).Select(Path.GetFileName); // Get all image file names

                foreach (string fn in filenames)
                {
                    listBox1.Items.Add(fn); // Add all image file names to listbox 
                }               

        }
    }

 private void Form1_KeyDown(object sender, KeyEventArgs e)
    {           
        if (e.KeyCode == Keys.Enter) //Go to next image after press Enter key
        {            
            if (listBox1.SelectedIndex != listBox1.Items.Count - 1)
            {
                listBox1.SelectedIndex = listBox1.SelectedIndex + 1;
            }

            e.SuppressKeyPress = true;
        }
     }

 private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
         Index = listBox1.SelectedIndex; //Get selected item from listbox
         pictureBox1.Image = Image.FromFile(array[Index].ToString()); // display image to picturebox 
    }
Quang Nguyen
  • 1
  • 1
  • 1

3 Answers3

0

You are using Image.FromFile, which loads the image from the disk. IO operations like this will usually be slowish because they rely on the disk speed to complete.

I would suggest loading all .tif images from the folder via another thread when they are added to the listbox.

This will have some initial loading time to load all of the images, but after that you could click on any list item and they will appear instantaneously.

Example: After adding the image names to the listbox, run this code as a new thread: (Make sure you have a global variable List<Bitmap> Images that contains all of your loaded images)

Images = new List<Bitmap>(); //Or use an array, your choice.
foreach (string file in filearray)
{
     Images.Add(Image.FromFile(file));
}

Then when you select an item from the listbox, set the image like so:

pictureBox1.Image = Images.ElementAt[Index];

Note that this may not work if your images are extremely large, as loading them all at once may cause an out of memory error.

If this is the case, you will need to load them as they are clicked, and put up with the loading time. Because you have an i7, many end users may have a slower computer that will take even longer. This is why I suggest using a threaded operation to load them in the background, and provide a loading bar or animation, as loading large tiff files will not be instant.

I'm not going to bloat this post with information about threading, because the answer on this S.O. question shows a great way to handle operations in the background, while reporting progress (Ex: Loading bar), to your users.

In short: It's better to either load them all at once, if small enough, or when you need to. There is no way to get around loading time, so you will either need to put up with a small wait, or (recommended), run the loading in a new thread and provide a loading image for users while the image(s) are being loaded.

Community
  • 1
  • 1
Cyral
  • 13,999
  • 6
  • 50
  • 90
0

Image Loader Test Results: ImageFromFileFast() is about 800% times faster than ImageFromFileSlow() (keep in mind that ImageFromFileFast() is unsafe code)

Image Loaded time using the ImageFromFileSlow() is 4351.1889 milliseconds
Image Loaded time using the ImageFromFileFast() is 541.965 milliseconds
ImageFromFileFast() is 802.85% faster than ImageFromFileSlow()

Image Loader Test:

        string BigImage19MBpath = @"H:\Earth's_Location_in_the_Universe_(JPEG).jpg";
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Bitmap LoadedImage = (Bitmap)ImageFromFileSlow(BigImage19MBpath);
        double TotalMillisecondsSlow = sw.Elapsed.TotalMilliseconds;
        Debug.WriteLine(string.Format("Image Loaded time using the ImageFromFileSlow() is {0} milliseconds", TotalMillisecondsSlow.ToString()));

        sw.Restart();                       
        Bitmap LoadedImage1 = (Bitmap)ImageFromFileFast(BigImage19MBpath);
        double TotalMillisecondsFast = sw.Elapsed.TotalMilliseconds;
        Debug.WriteLine(string.Format("Image Loaded time using the ImageFromFileFast() is {0} milliseconds", TotalMillisecondsFast.ToString()));
        Debug.WriteLine(string.Format("ImageFromFileFast() is {0}% faster then ImageFromFileSlow()", (100*TotalMillisecondsSlow/TotalMillisecondsFast).ToString("0.00")));

Image Loder Functions:

    [DllImport("Kernel32.dll", EntryPoint = "CopyMemory")]
    private static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
    /// <summary>
    /// loads image fast but UNSAFE
    /// </summary>
    /// <param name="iPath"></param>
    /// <returns></returns>
    public static Image ImageFromFileFast(string iPath)
    {
        using (Bitmap sourceImage = (Bitmap)Image.FromFile(iPath))
        {
            Bitmap targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, sourceImage.PixelFormat);
            BitmapData sourceBitmapData = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, sourceImage.PixelFormat);
            BitmapData targetBitmapData = targetImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.WriteOnly, targetImage.PixelFormat);
            CopyMemory(targetBitmapData.Scan0, sourceBitmapData.Scan0, Convert.ToUInt32(sourceBitmapData.Stride) * Convert.ToUInt32(sourceBitmapData.Height));
            sourceImage.UnlockBits(sourceBitmapData);
            targetImage.UnlockBits(targetBitmapData);
            return targetImage;
        }
    }

    public static Image ImageFromFileSlow(string iPath)
    {
        using (Bitmap sourceImage = (Bitmap)Image.FromFile(iPath))
        {
            Bitmap targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, sourceImage.PixelFormat);
            using (Graphics g = Graphics.FromImage(targetImage))
            {
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

                g.DrawImage(sourceImage, 0, 0, sourceImage.Width, sourceImage.Height);
            }
            return targetImage;
        }
    }
khan
  • 4,479
  • 1
  • 15
  • 9
0

An image of 3184x4208 (from your comments) takes approximately 40 Mb of memory

If the user quickly browses/opens these images, let's say 1 per second, the application would claim 2.4 Gigabyte within a minute (by opening/browsing 60 images).

If you do not free the claimed memory this will cause the application to run out of memory and slowing down considerably. Read this question and answer on on cleaning up: .NET Memory issues loading ~40 images, memory not reclaimed, potentially due to LOH fragmentation

Depending on the actual functionality of your program you might resort to creating thumbnails and loading these. That will claim less memory (you will still have to free it) and will load a lot faster. See this question with multiple suggestions on how to create thumbnails: Generating image thumbnails in ASP.NET? (the question is about a web application but it is still discussing the same issue)

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115