Russian Qt Forum

Qt => Общие вопросы => Тема начата: Vladimir от Август 13, 2013, 14:02



Название: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 14:02
Доброго дня! Как добиться максимальной эффективности при сохранении картинки, не останавливая основоное приложение на 0.5-1 сек. Хотелось разместить код
Код:
QPixmap px = QPixmap::grabWindow(QApplication::desktop()->winId()); 
px.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");  

в отдельном потоке с минимальным приоритетом, но он не работает в отдельном потоке видимо из-за непотокобезопасности QPixmap, софт вылетает.. программа под Linux. Должна делать скриншоты периодически раз в 10-20 сек (в режиме автомата), либо вручную.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Bepec от Август 13, 2013, 14:14
Платформо-ориентированным кодом будет наиболее быстро.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 14:21
А примеров под Linux случаем нет?))


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Bepec от Август 13, 2013, 14:30
Не работаю. Нехватает цели для использования linux.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 13, 2013, 15:23
Я делал так:
Запускаем функцию в потоке, а из нее делаем блокирующий вызов функции из главного потока через
Код:
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));

Таким образом никакого торможения гуи нет.

Посмотреть код можно тут: https://github.com/kibsoft/QtMEL/blob/master/src/grabbers/image/screengrabber.cpp
Функция captureFrame работает в отдельном потоке(сделано с помощью QtConcurrent), а функция currentFrame(к которой мы делаем блокирующий вызов) находится в главном.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 13, 2013, 15:28
Цитировать
Платформо-ориентированным кодом будет наиболее быстро.
А разве grabWindow не реализована платформо-зависимым кодом внутри? Я думаю других вариантов нет. В начале пробовал изобретать велосипед и делать скриншоты через WinAPI, но скорость та же самая, что и у grabWindow.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Bepec от Август 13, 2013, 15:35
В реализации Qt происходит много преобразований. Один уж QPixmap::fromWinHBITMAP чего стоит.

 Зачем преобразовывать, если нужно просто сохранить на диск winApi-шными функциями.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 15:39
Я делал так:
Запускаем функцию в потоке, а из нее делаем блокирующий вызов функции из главного потока через
Код:
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));

Таким образом никакого торможения гуи нет.

Посмотреть код можно тут: https://github.com/kibsoft/QtMEL/blob/master/src/grabbers/image/screengrabber.cpp
Функция captureFrame работает в отдельном потоке(сделано с помощью QtConcurrent), а функция currentFrame(к которой мы делаем блокирующий вызов) находится в главном.

что-то не совсем понятно, как этот код работает.. как картинка сохраняется на жесткий диск? тут ведь и есть самая большая потеря времени.. из-за которого и рубиться приложение. если картинка на экране с минимум цветов, то сохранение относительно без тормозов проходит.. но если много цветов на экране, приложение просто замирает!


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 13, 2013, 15:46
Код:
QImage frame;
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");

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

Код:
QImage your_function() {
   return QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
}

А эта функция должна находится в главном потоке.


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 15:51
Код:
QImage frame;
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");

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

Код:
QImage your_function() {
   return QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
}

А эта функция должна находится в главном потоке.

о как! сейчас буду пробовать..


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 16:20
При вызове функции создания скрина на консоль выдает:
QMetaObject::invokeMethod: No such method CThreadSaveScreen::getScreenShot()

Я так понимаю надо зарегить объект CThreadSaveScreen, как мета тип
qRegisterMetaType< CThreadSaveScreen >( "CThreadSaveScreen" ); т.о.?

на это ругается ошибкой: 'QObject::QObject(const QObject&)' is private

класс CThreadSaveScreen:
Код:
class CThreadSaveScreen : public QThread
{
    Q_OBJECT

    private:
    QString pathScreen;

    public:
        CThreadSaveScreen() : QThread()
        {
            pathScreen = "";
        }

    protected:
        void run();

    private slots:
        void setPathScreen(QString path);
};


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: _OLEGator_ от Август 13, 2013, 16:33
А где у класса CThreadSaveScreen метод getScreenShot?


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 13, 2013, 16:41
Код:
[quote author=kibsoft link=topic=25451.msg182211#msg182211 date=1376397980]
[code]QImage frame;
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");

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

Код:
QImage your_function() {
   return QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
}

А эта функция должна находится в главном потоке.
[/quote]

так я так понял, что your_function() - это функция класса основного потока?
А
Код:
QImage frame;
QMetaObject::invokeMethod(this, "[b]your_function[/b]", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");
[/code]
делаем в run() другого потока..


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 13, 2013, 19:58
Стоит почитать документацию. Код, который в run() выполняется в отдельном потоке, а все остальные члены класса QThread находятся в главном потоке(если объект CThreadSaveScreen создан там и не перемещен в другой поток). Таким образом, в run вы выполняете это:

Код:
QImage frame;
QMetaObject::invokeMethod(this, "your_slot", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");

И объявляете приватный слот в CThreadSaveScreen под название your_slot:
Код:
private slots:
QImage your_slot()
{
   return QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
}


Название: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 14, 2013, 10:43
Стоит почитать документацию. Код, который в run() выполняется в отдельном потоке, а все остальные члены класса QThread находятся в главном потоке(если объект CThreadSaveScreen создан там и не перемещен в другой поток). Таким образом, в run вы выполняете это:

Код:
QImage frame;
QMetaObject::invokeMethod(this, "your_slot", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
frame.save(currentFile.at(0) + "/" + currentFile.at(1) + ".png");

И объявляете приватный слот в CThreadSaveScreen под название your_slot:
Код:
private slots:
QImage your_slot()
{
   return QPixmap::grabWindow(QApplication::desktop()->winId()).toImage();
}

Спасибо kibsoft! Все получилось и работает шустро! Правда на слот QImage your_slot() он заругался
QMetaMethod::invoke: Unable to invoke methods with return values in queued connections почему-то.. поставил ему void!
Работает все как и хотелось!  ;D







Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: mutineer от Август 14, 2013, 10:46
Заругался потому, что не в курсе куда ему девать возвращаемое значение


Название: Re: Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 14, 2013, 10:50
Заругался потому, что не в курсе куда ему девать возвращаемое значение

Ааа понятно.


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: Vladimir от Август 14, 2013, 10:55
А можете объяснить что делает
QMetaObject::invokeMethod(this, "your_function", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, frame));
в целом.. с такой конструкцией встречался редко..
И что вообще за класс такой QMetaObject?


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: mutineer от Август 14, 2013, 11:04
У объекта this вызывает метод "your_function" через QueuedConnection и ждет окончания работы вызванного метода


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 14, 2013, 11:36
Цитировать
Спасибо kibsoft! Все получилось и работает шустро! Правда на слот QImage your_slot() он заругался
QMetaMethod::invoke: Unable to invoke methods with return values in queued connections почему-то.. поставил ему void!
Скорее всего потому что использовали Qt::QueuedConnection вместо Qt::BlockingQueuedConnection.


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: mutineer от Август 14, 2013, 11:53
Цитировать
If type is Qt::BlockingQueuedConnection, the method will be invoked in the same way as for Qt::QueuedConnection, except that the current thread will block until the event is delivered.
Цитировать
The return value of the member function call is placed in ret. If the invocation is asynchronous, the return value cannot be evaluated.

По доке создается ощущение что даже с Qt::BlockingQueuedConnection получить результат не удастся


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: panAlexey от Август 14, 2013, 15:28
Интересно, надо посмотреть.


Название: Re: [РЕШЕНО] Сделать скрин экрана максимально эффективно!
Отправлено: kibsoft от Август 14, 2013, 17:57
Все работает. Я уже выше приводил ссылку на реализацию.