Как эффективно изменить размер изображения в C #? - PullRequest
0 голосов
/ 08 сентября 2018

Я искал около 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 голосов
/ 08 сентября 2018

Ну, я не знаю, насколько это вам поможет, но оно того стоит. Я не совсем уверен, что WebClient можно распараллелить подобным образом, но аналогичный пример появляется на странице 187 Pro .NET Performance: Optimize Your C# Applications, поэтому я собираюсь сделать прыжок и сказать, что вы можете использовать WebClient из нескольких потоков. Я добавил новый метод, который вы можете просто вызвать из своего пользовательского интерфейса, хотя он делает проверку, чтобы убедиться, что его не нужно вызывать. Этот метод считывает номер страницы, а затем запускает фоновый процесс, который загружает и изменяет размеры изображений, а затем, после того, как он все это сделает, он создаст элементы управления в потоке пользовательского интерфейса.

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 новых. Что было бы лучше, так это использовать тот же подход, который я показал в приведенном выше коде, и затем вместо «создания» новых элементов управления вы просто обновляете существующие. Вы можете добавить метод обновления в свой пользовательский элемент управления, который просто изменяет изображение и элемент «Фильм», который хранится в элементе управления. Это позволит сэкономить на создании новых элементов управления каждый раз, что должно повысить производительность. Вам может потребоваться вызвать 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;
    }
}
...