Arrow
Arrow17 апреля 2017 г. 5:36

Оптимизация загрузки изображений

Оптимизация загрузки изображений, QPixmap

Приложение загружает для просмотра изображения с указанной пользователем папки или нескольких папок. Загрузка происходит в отдельном потоке.

Загрузить выходит максимум 20 шт., если больше, то программа на Debian начинает тормозить, а под Windows 7 и вовсе отказывается что-то грузить (грузит 20 штук и дальше ни в какую).

Пробовал использовать частичную загрузку по 15 шт., а дальше по мере прокрутки QScrollArea пользователем догружать остатки - как оказалось не вариант пользователь может перейти к любому изображению (прокрутить скрол). При этом чувствуются тормоза (не успевает все прогрузится) и программа занимает много памяти.

Пробовал также удалить прошлые изображения при до-загрузке новых. В общем возникает масса неудобств и нет возможности навигации по всем изображениям и те же тормоза.

Отсюда назрел вопрос: Есть ли какая-то возможность оптимизировать процесс загрузки или как-то уменьшить вес изображений при их загрузке на отображение?

Пытался сделать так, но не помогло (изображение после компрессии не загружается в pixmap):

        QPixmap pix;

//------------------------------------------------------------------------------

        QByteArray array;
        QBuffer buf(&array, this);
        buf.open(QIODevice::WriteOnly);

        pix.save(&buf, "JPG", 30);
        buf.close();
        array = qCompress(array, 9);

        pix.loadFromData(array, "JPG");   // Здесь pix.size() - 0
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

11
Evgenii Legotckoi
  • 17 апреля 2017 г. 7:03

qCompress однозначно использовать нельзя, поскольку он предназначен просто для компрессии сырых байтов без учёта какого-либо алгоритма организации данных, в данном случае он игнорирует структуру организации данных изображений. Поэтому от такого способа оптимизации сразу отказываемся. Этот инструмент для этого не предназначен.

Что касается остальной части вопроса, то нужно подумать. Вечером может какие мысли появятся, ну и или другие пользователи форума может что подскажут.

    Evgenii Legotckoi
    • 18 апреля 2017 г. 1:19

    А какого вообще размера изображения?

    Вы работаете с изображениями, поэтому программа в любом случае будет потреблять большие объёмы памяти, особенно, если это фотографии в высоком качестве, поэтому здесь только смириться, или уменьшать размер фото, вместе с качеством фото.

    К слову говоря, метод QPixmap::scaledToWidth автоматически уменьшает размер изображения и его качество, думаю, что на потреблении памяти это тоже сказывается, поскольку необходимо держать меньшее количество пикселей в памяти. В принципе вопрос потребления памяти можно несколько нивелировать, если хранить в памяти не оригиналы изображения, а пути к изображению, а в случае изменения размеров каждый погружать изображение из этого пути и масштабировать. Хотя, конечно, тут могут появиться задержки при открытии файла. Что касается подгрузки изображений. Вы замечали, что в файловых менеджерах превью изображений сначала выглядит как обычная иконка, а потом только погружается как положено. Можете применить данный способ отображения. То есть заполнить всё сначала некоторыми иконками, которые подразумевают изображение, а потом уже заполнять картинку, когда пользователь прокрутит. Это несколько сгладит когнитивный диссонанс у пользователя.

    Тем более, если вернуться к просмоторщикам, тот же самый встроенный просмоторщик pdf в Linux тоже не отличается идеальной скоростью при открытии очень больших pdf. Так что некоторые задержки будут всегда, особенно на слабом железе.

    Но если у вас программа падает или виснет всего на 20 изображениях, то это говорит о проблемах в логике кода. То есть это скорее не вопрос оптимизации, как я полагаю. Например, в одном из наших обсуждений по этой тематике, я же набросал пробную программку со скроллингом изображений. Она вполне неплохо открывает 200-300 фотографий и не виснет. Хотя я понимаю, что у вас логика уже явно сложнее, но это лишь говорит о том, что где-то, что-то вы делаете не так. Да, в моём варианте видно, что фотографии грузятся, а не всё сразу отображается, но для этого и служат всякие прогресс бары, чтобы показываться процесс загрузки. Поэтому подумайте, над тем, что я написал. Задача скорее не в том, чтобы оптимизировать, а в том, чтобы найти ошибку, поскольку 20 изображений - это не такая большая нагрузка, чтобы повешать приложение на современно ПК, а открываться эти 20 изображении будут секунды, не более.

      Evgenii Legotckoi
      • 18 апреля 2017 г. 1:24
      • Ответ был помечен как решение.

      Вот, прикладываю мою пробную программку, просто посмотрите как она работает на большом фото архиве. Где фотографий 300 будет. Там кнопка на открытие диалога, в диалоге переходите в каталог с фотками и выделяете все фотографии.

      Посмотрите, как будет грузиться. Это вполне приемлемый по скорости процесс загрузки.

        Arrow
        • 18 апреля 2017 г. 8:11

        Спасибо!

        У меня грузит с такой же скоростью.

        Изображение: 2400х3400 (23,5 МБт).

        У меня происходит дополнительная обработка изображений и она как оказалось и дает нагрузку.

        Похоже от этого не уйти никак, придется смириться.

        Если показывать подставную картинку выходит очень даже ничего. :)

        Пара вопросов по вашему коду:

        1. Из-за любопытства (чисто для себя)- Почему вы некоторые заголовочные файлы подключаете в файле реализации (*.cpp), а не в заголовочном (*. h) и где правильнее?

        2. По незнанию всех функций Qt:

        // Почему так
        
        QString str = QFileDialog::getOpenFileName(this,
                                    "Выберите папку с изображениями",
                        QStandardPaths::locate(QStandardPaths::HomeLocation, QString()),
                                    "Изображения (*.jpg | *.bmp | *. png | *.tif)");
        
        // А не так
        
        QString str = QFileDialog::getOpenFileName(this,
                                    "Выберите папку с изображениями",
                        QDir::homePath(),
                                    "Изображения (*.jpg | *.bmp | *. png | *.tif)");
          Evgenii Legotckoi
          • 18 апреля 2017 г. 8:42

          Если изображение 23 Мб весит, то тут да... тяжко будет открывать любому редактору.

          Думаю, что если разобраться с дополнительной обработкой, то можно будет найти какое-нибудь решение, которое ещё уменьшит нагрузку, например, убрать лишние действия, вполне возможно, если вы вернётесь например через пару недель к тому участку кода, то увидите свежим взглядом более элегантное и менее затратное решение, но работа с графикой в принципе накладный процесс сам по себе.

          Могу только посоветовать ещё порыть в направлении распараллеливания подгрузки изображений по ядрам процессора, то есть не просто в одном отдельном потоке всё делать, а делать в 4-х потоках, если процессор 4-х ядерный. Посмотрите в сторону QThreadPool . К сожалению, у меня статей на эту тему нет, но подумаю, может на теме изображений и скроллинга накидаю статью с QThreadPool

          Что касается прочих вопросов.

          1. Заголовочные файлы иных классов следует подключать в cpp файлах, только если эти классы не объявляются в заголовочном классе разрабатываемого файла. То есть Если QUrl не используется в объявлении класса или нет методов, которые его принимают или возвращают, но он используется в коде реализации, то заголовочный файл QUrl следует поместить в файл cpp. Это несколько увеличивает скорость компиляции, поскольку компилятору не требуется постоянно перепроверять, было ли что-то подключено в заголовочных файлах или нет. Нагрузка несколько снижается.

          2. QStandardPaths даёт более расширенный функционал по определению путей к ключевым директориям. Когда разбирался с этим под Андроид, то постоянно пользовался именно QStandardPaths . Почитайте документацию на этот класс, там много разных путей, а QDir ограничен только домашней директорией. Впрочем для использования домашней директории как раз хватает QDir::homePath() , но я просто изначально пользовался QStandardPaths , и даже внимания не обратил на данный метод при чтении документации, так что это из разряда "так получилось ". Так что спасибо, что обратили внимания на данный момент.

            Arrow
            • 18 апреля 2017 г. 9:13

            Я тут подумал, а что если сделать загрузку изображения не в QLabel, а через прорисовку квадрата QRect и на нем при помощи QPainter рисовать изображение.

            Возможно даже взять и вырезать некоторые пиксели из него (как именно пока не знаю, но такое используется в редакторах изображений при уменьшении размера изображения).

            Или это гиблое дело и ничего выиграть не выйдет?

              Evgenii Legotckoi
              • 18 апреля 2017 г. 9:28

              Вообще QRect - это просто геометрическая область, которая не имеет никакого понятия о рисовании. У него даже метода paintEvent() нет. Поэтому я не особо понимаю, что вы имели ввиду под этим. А так отрисовка что в QLabel , что в QWidget будет технически одинаково затратной, поскольку QLabel наследован от QWidget , да и техническая реализация будет ненамного больше отличаться, разве только у QWidget будете переопределять метод paintEvent() Да там реализовывать отрисовку изображения. Кода получится больше - это факт.

              Возможно, получится ускориться, если использовать QOpenGLWidget вместо QLabel. Тогда будет графическая подсистема задействоваться. Но это даст эффект только для отрисовки. А у вас ещё дополнительная обработка, которая ложится на CPU.

              Поэтому основные затраты у вас - это дополнительная обработка, вот она и влияет. А уменьшение количества пикселей и так скорее всего происходит во время выполнения метода scaledToWidth . Качество уж точно падает.

              Так что, наиболее эффективным сейчас будет - это внедрение многопоточности, то есть использование всех возможных процессоров на ПК. Именно дополнительную обработку нужно вынести в отдельные потоки.

                Arrow
                • 18 апреля 2017 г. 9:54

                Спасибо! Попытаюсь реализовать для начала два потока, посмотрю что даст.

                  Arrow
                  • 19 апреля 2017 г. 8:34

                  Вы пишите, что можно распараллелить подгрузку изображений по ядрам процессора при помощи QThreadPool. Таипа такого:

                  class ImageLoader : public QObject, public QRunnable
                  
                  //-----------------------------------
                  
                  ImageLoader *imgLoader = new ImageLoader();
                  
                      // Обработка изображений
                      connect(imgLoader, &ImageLoader::sendPixmap, this, &MainWindow::addImage);
                  
                      QThreadPool::globalInstance()->start(imgLoader);

                  Чем этот метод отличается от QThread:

                  class ImageLoader : public QObject
                  
                  //-------------------------------------------------------
                  
                     QThread* thread = new QThread;
                      ImageLoader *imgLoader = new ImageLoader();
                  
                      connect(thread, &QThread::started, imgLoader, &ImageLoader::process);
                  
                      connect(imgLoader, &ImageLoader::finished, thread, &QThread::terminate);
                  
                      connect(thread, &QThread::finished, imgLoader, &ImageLoader::deleteLater);
                  
                      connect(imgLoader, &ImageLoader::sendPixmap, this, &MainWindow::addImage);
                  
                      imgLoader->moveToThread(thread);
                      thread->start();

                  И что в данном случае более правильно использовать?

                    Evgenii Legotckoi
                    • 19 апреля 2017 г. 12:47

                    Метод с QThreadPool отличается тем, что по умолчанию работает с количеством потоков равным количеству процессоров на ПК. Метода QThreadPool::idealThreadCount() позволяет узнать, сколько процессоров имеется в системе. В случае же с QThread, нужно будет следить за количеством процессоров и создавать необходимое количество этих потоков а потом удалять их. QThreadPool же делает это в автоматическом варианте.

                    Фактически нужно будет в зависимости от количества ядер процессора создать несколько объектов ImgLoader и поместить их в QThreadPool . Важным моментом для Вас будет то, что необходимо будет разделить весь список путей к файлам на количество процессоров. То есть, если берём 120 файлов, а количество процессоров(ядер) равно 4, то нужно будет создать 4 ImgLoader, каждый из которых будет обрабатывать по 30 файлов из этих 120. Ну и добавить немного логики, чтобы эти ImgLoader не передрались друг с другом, а также все изображения загружались в нужной последовательности.

                    В вашей задаче будет правильнее использовать QThreadPool, поскольку здесь не просто убираем в отдельные потоки всю логику по загрузке картинок, а ещё её нужно и распределить по потокам, чтобы ускорить процесс. Ну а вариант с одним QThread можно использовать для задач попроще, например, когда нужно проверять какое-то одно событие, которое имеет смысл обрабатывать в отдельном потоке. Зачастую такое необходимо, чтобы увязать с программой на Qt некие внешние SDK, которые ещё могут быть написаны и на Си.

                      Arrow
                      • 19 апреля 2017 г. 15:09

                      Понял, спасибо!

                        Комментарии

                        Только авторизованные пользователи могут публиковать комментарии.
                        Пожалуйста, авторизуйтесь или зарегистрируйтесь
                        OI
                        • Ora Iro
                        • 24 декабря 2024 г. 14:38

                        C++ - Тест 001. Первая программа и типы данных

                        • Результат:40баллов,
                        • Очки рейтинга-8
                        AD

                        C++ - Тест 004. Указатели, Массивы и Циклы

                        • Результат:50баллов,
                        • Очки рейтинга-4
                        m
                        • molni99
                        • 26 октября 2024 г. 8:37

                        C++ - Тест 004. Указатели, Массивы и Циклы

                        • Результат:80баллов,
                        • Очки рейтинга4
                        Последние комментарии
                        ИМ
                        Игорь Максимов22 ноября 2024 г. 19:51
                        Django - Урок 017. Кастомизированная страница авторизации на Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
                        Evgenii Legotckoi
                        Evgenii Legotckoi31 октября 2024 г. 21:37
                        Django - Урок 064. Как написать расширение для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
                        A
                        ALO1ZE19 октября 2024 г. 15:19
                        Читалка fb3-файлов на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
                        ИМ
                        Игорь Максимов5 октября 2024 г. 14:51
                        Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
                        d
                        dblas55 июля 2024 г. 18:02
                        QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                        Сейчас обсуждают на форуме
                        Evgenii Legotckoi
                        Evgenii Legotckoi24 июня 2024 г. 22:11
                        добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
                        t
                        tonypeachey115 ноября 2024 г. 14:04
                        google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
                        NSProject
                        NSProject4 июня 2022 г. 10:49
                        Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
                        9
                        9Anonim25 октября 2024 г. 16:10
                        Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

                        Следите за нами в социальных сетях