如何在C#中有效地调整图像大小?

时间:2018-09-07 23:11:05

标签: c# image winforms picturebox

我正在搜索大约4个月,以解决我的180个控件(类似于图像框)的图像闪烁问题,这些控件已插入流式布局面板中。 我几乎尝试了所有操作(启用双重闪烁等),但对我没有任何帮助。但是,今天我想出了一个解决方案,那就是将图像大小模式设置为正常模式,而不是缩放或调整图像大小以完全适合类似于图片框的控件。所以我选择了第二个选项,这是我的代码:

public MovieControl(Movies M1, bool show, MainForm current)
{
    InitializeComponent();
    Current = current;
    if (M1.movie_Banner == "") bunifuImageButton1.Image = Properties.Resources.no_image_available;
    else
    {
        WebClient client = new WebClient();
        Stream stream = client.OpenRead(M1.movie_Banner);
        Bitmap bitmap; bitmap = new Bitmap(stream);
        if (bitmap != null)
            bunifuImageButton1.Image = new Bitmap((Image)bitmap, new Size(139, 208));
        stream.Flush();
        stream.Close();
        client.Dispose();
    }
    bunifuImageButton1.Tag = M1;
    M1 = null;
}

此外,我想提到的是,线程使用以下几行来调用此代码:

private void RunMoviesThread()
{
    ClearMovies();
    LoadMoviesThread = new Thread(() => LoadMoviesControls());
    LoadMoviesThread.Start();
}

private void LoadMoviesControls()
{
    int x = 1;
    if (MPageDropDown.InvokeRequired)
    this.Invoke((MethodInvoker)delegate { if (MPageDropDown.SelectedItem != null)  x = int.Parse(MPageDropDown.SelectedItem.ToString()); });
    else if (MPageDropDown.SelectedItem != null) x = int.Parse(MPageDropDown.SelectedItem.ToString());
    for (int i = (x - 1) * Max; i < MoviesList.Count() && i < x * Max; i++)
    {
        MovieControl tmp = new MovieControl(MoviesList[i], ShowMError, this);
        if (tmp.InvokeRequired || MoviesFlowPanel.InvokeRequired)
        {
            MovieControl m1 = null;
            try
            {
                m1= new MovieControl(MoviesList[i], ShowMError, this);
            }
            catch { }
            this.Invoke((MethodInvoker)delegate { MoviesFlowPanel.Controls.Add(m1); });
        }
        else
            MoviesFlowPanel.Controls.Add(tmp);
            tmp = null;
    }
}

效果很好,而不是花费很长时间来处理。例如;在极少数情况下,图像可能需要大约半秒,最多20秒!你能帮我找到一种更有效的方法吗? 请注意:我的图像以前是通过互联网上传的,而此代码是用C#编写的。此外,无需调整大小,加载过程仅需几秒钟。 **最大值设置为180 预先感谢。

1 个答案:

答案 0 :(得分:0)

我不知道这对您有多大帮助,但是值得一试。我并不完全相信WebClient可以像这样并行化,但是在Pro .NET Performance: Optimize Your C# Applications的第187页上出现了一个类似的示例,因此我将大步走说可以使用WebClient来自多个线程。我添加了一个新方法,尽管可以进行检查以确保不需要调用它,但您只能从UI调用它。此方法读取页码,然后启动后台进程,该进程将下载图像并调整其大小,然后在完成所有操作后,将在UI线程中创建控件。

private void GetPageAndLoadControls()
{
    if (MPageDropDown.InvokeRequired)
    {
        MPageDropDown.Invoke((MethodInvoker)(() => GetPageAndLoadControls();));
        return;
    }

    var page = MPageDropDown.SelectedItem != null ?
        int.Parse(MPageDropDown.SelectedItem.ToString()) - 1 : 0;
    Task.Run(() => LoadMoviesControls(page));
}

private void LoadMoviesControls(int page)
{
    var dict = new ConcurrentDictionary<string, Bitmap>();
    var movies = MovieList.Skip(page * Max).Take(Max).ToList();
    using (var client = new WebClient())
    {
        Parallel.ForEach(movies, (m) =>
        {
            Stream stream = null;
            Bitmap bmp = null;
            try
            {
                if (!string.IsNullOrWhitespace(m.movie_Banner)
                {
                    stream = client.OpenRead(s);
                    bmp =  new Bitmap(stream);
                    // Note: I am guessing on a key here, that maybe there is a title
                    // use whatever key is going to be best for your class
                    dict.TryAdd(m.movie_Title, new Bitmap(bmp, 139, 208));
                }
                else dict.TryAdd(m.movie_Title, Properties.Resources.no_image_available);
            }
            finally
            {
                bmp?.Dispose();
                stream?.Dispose();
            }
        });
    }

    // Here we have to invoke because the controls have to be created on
    // the UI thread. All of the other work has already been done in background
    // threads using the thread pool.
    MoviesFlowPanel.Invoke((MethodInvoker)() =>
    {
        foreach(var movie in movies)
        {
            Bitmap image = null;
            dict.TryGetValue(movie.movie_Title, out image);
            MoviesFlowPanel.Controls.Add(
                new MovieControl(movie, image, ShowMError, this);
        }
    });
}

// Changed the constructor to now take an image as well, so you can pass in
// the already resized image
public MovieControl(Movies M1, Bitmap image, bool show, MainForm current)
{
    InitializeComponent();
    Current = current;
    bunifuImageButton1.Image = image ?? Properties.Resources.no_image_available; // Sanity check
    bunifuImageButton1.Tag = M1;
}

修改
在编写此代码并对其进行了更多思考之后,我发生了一些事情,您没有发布ClearMovies()的代码,但是从发布的代码中,我假设您正在处置之前的180个控件并创建180个新控件。最好是使用上面代码中显示的相同方法,然后代替“创建”新控件,而只是更新现有控件。您可以向用户控件添加一个更新方法,该方法仅更改图像和存储在控件中的Movie项目。这样可以节省每次创建新控件的开销,从而可以提高性能。您可能需要在Invalidate方法的末尾调用Update。不确定,由于我无法真正测试所有内容,因此只想提一下。另外,我不会使用Tag属性来保存Movie对象,而是将一个成员变量添加到您的UserControl中。这将增加类型安全性,并使其更清楚地保存在何处。

public class MovieControl : Control
{
    public Movies Movie { get; protected set; }

    // Changed the constructor to now take an image as well, so you can
    // pass in the already resized image
    public MovieControl(Movies M1, Bitmap image, bool show, MainForm current)
    {
        InitializeComponent();
        Current = current;
        bunifuImageButton1.Image = image ?? Properties.Resources.no_image_available; // Sanity check
        Movie = M1;
    }

    public void UpdateMovieAndImage(Movies movie, Image image)
    {
        // Only dispose if it isn't null and isn't the "No Image" image
        if (bunifuImageButton1.Image != null
            && bunifuImageButton1.Image != Properties.Resources.no_image_available)
        {
            binifuImageButton1.Image.Dispose();
        }
        bunifuImageButton1.Image = image;
        Movie = movie;
    }
}