【问题标题】:Display images from selected item in Listbox is slow c#在列表框中显示所选项目的图像很慢 c#
【发布时间】:2014-07-25 19:28:38
【问题描述】:

我创建了一个简单的程序,将 Listbox1 中选定项目的图像显示到图片框1。它工作正常,但显示下一张图像大约需要 200 毫秒。我的电脑使用的是 Intel i7 处理器和 Windows 7 64 位。请告诉我如何加快这个过程。以下是我的代码。谢谢!

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 
    }

【问题讨论】:

  • 原始图像有多大(以字节为单位)?
  • 200 毫秒?用户会睡着的!
  • @Erno de Weerd:每个文件夹大约有 1000 张图像。每张图片的分辨率为 300 dpi。
  • 抱歉,我们需要以 Kb 为单位的大小和大小(像素!)而不是 DPI
  • 大小在100KB到300KKb之间,尺寸为3184x4208

标签: c# image listbox picturebox


【解决方案1】:

您正在使用Image.FromFile,它从磁盘加载图像。像这样的 IO 操作通常会很慢,因为它们依赖于磁盘速度来完成。

我建议将文件夹中的所有.tif 图像添加到列表框后,通过另一个线程加载它们。

这将有一些初始加载时间来加载所有图像,但之后您可以单击任何列表项,它们会立即出现。

示例:将图像名称添加到列表框后,将此代码作为新线程运行:(确保您有一个包含所有已加载图像的全局变量 List<Bitmap> Images

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

然后当你从列表框中选择一个项目时,像这样设置图像:

pictureBox1.Image = Images.ElementAt[Index];

请注意,如果您的图片非常大,这可能不起作用,因为一次加载它们可能会导致内存不足错误。

如果是这种情况,您将需要在单击它们时加载它们,并忍受加载时间。因为您拥有 i7,所以许多最终用户的计算机速度可能会更慢,需要更长的时间。这就是为什么我建议使用线程操作在后台加载它们,并提供加载栏或动画,因为加载大型 tiff 文件不会是即时的。

我不会用有关线程的信息来夸大这篇文章,因为this S.O. 上的答案。问题展示了一种在后台处理操作的好方法,同时向您的用户报告进度(例如:加载栏)。

简而言之:最好一次加载它们,如果足够小,或者在需要时加载。没有办法绕过加载时间,因此您要么需要稍等片刻,要么(推荐)在新线程中运行加载,并在图像正在加载时为用户提供加载图像已加载。

【讨论】:

  • @Cyral:感谢您的建议。我会尝试让你知道它是否有效。
【解决方案2】:

图像加载器测试结果:ImageFromFileFast()ImageFromFileSlow()800% 倍(请记住, ImageFromFileFast() 是不安全的代码)

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()

图像加载器测试:

        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")));

图像加载器功能:

    [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;
        }
    }

【讨论】:

    【解决方案3】:

    一张 3184x4208 的图像(来自您的 cmets)大约需要 40 Mb 的内存

    如果用户快速浏览/打开这些图像,假设每秒 1 个,应用程序将在一分钟内占用 2.4 GB(打开/浏览 60 个图像)。

    如果您不释放声明的内存,这将导致应用程序耗尽内存并显着降低速度。 阅读有关清理的问题和答案:.NET Memory issues loading ~40 images, memory not reclaimed, potentially due to LOH fragmentation

    根据程序的实际功能,您可能会求助于创建缩略图并加载它们。这将占用更少的内存(您仍然必须释放它)并且加载速度会更快。有关如何创建缩略图的多个建议,请参阅此问题:Generating image thumbnails in ASP.NET?(问题是关于 Web 应用程序,但仍在讨论同一问题)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-22
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 2016-08-31
      • 2013-09-21
      相关资源
      最近更新 更多