Commit 37b71620 authored by Alvin Wong's avatar Alvin Wong

Work around low framerate of stabilizer

Work around low framerate of stabilizer by delaying and painting the
stroke progressively.

The `KisStabilizerDelayedPaintHelper` class collects the sampled events
and distributes them evenly with a timestamp attached, then a timer firing
at a closer interval would paint the line bit by bit in order to give a
smoother user feedback.

The config option `stabilizerDelayedPaintInterval` with default value `20`
controls the paint interval. The delayed painting is disabled if this
value is higher than `stabilizerSampleSize`, which means it is disabled by
default on non-Windows system.

BUG: 369349
CLose: T4182
Differential Revision: https://phabricator.kde.org/D3321
parent e8b4c7ea
......@@ -199,6 +199,7 @@ set(kritaui_LIB_SRCS
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
......
......@@ -1693,6 +1693,19 @@ void KisConfig::setStabilizerSampleSize(int value)
m_cfg.writeEntry("stabilizerSampleSize", value);
}
int KisConfig::stabilizerDelayedPaintInterval(bool defaultValue) const
{
const int defaultInterval = 20;
return defaultValue ?
defaultInterval : m_cfg.readEntry("stabilizerDelayedPaintInterval", defaultInterval);
}
void KisConfig::setStabilizerDelayedPaintInterval(int value)
{
m_cfg.writeEntry("stabilizerDelayedPaintInterval", value);
}
QString KisConfig::customFFMpegPath(bool defaultValue) const
{
return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString());
......
......@@ -482,6 +482,9 @@ public:
int stabilizerSampleSize(bool defaultValue = false) const;
void setStabilizerSampleSize(int value);
int stabilizerDelayedPaintInterval(bool defaultValue = false) const;
void setStabilizerDelayedPaintInterval(int value);
QString customFFMpegPath(bool defaultValue = false) const;
void setCustomFFMpegPath(const QString &value) const;
......
/*
* Copyright (c) 2016 Alvin Wong <alvinhochun-at-gmail-com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisStabilizerDelayedPaintHelper.h"
KisStabilizerDelayedPaintHelper::TimedPaintInfo::TimedPaintInfo(int elapsedTime, KisPaintInformation paintInfo)
: elapsedTime(elapsedTime)
, paintInfo(paintInfo)
{
}
KisStabilizerDelayedPaintHelper::KisStabilizerDelayedPaintHelper()
{
connect(&m_paintTimer, SIGNAL(timeout()), SLOT(stabilizerDelayedPaint()));
}
void KisStabilizerDelayedPaintHelper::start(int paintInterval, const KisPaintInformation &firstPaintInfo) {
if (running()) {
cancel();
}
m_paintTimer.setInterval(paintInterval);
m_paintTimer.start();
m_elapsedTimer.start();
m_lastPendingTime = m_elapsedTimer.elapsed();
m_paintQueue.enqueue(TimedPaintInfo(m_lastPendingTime, firstPaintInfo));
}
void KisStabilizerDelayedPaintHelper::update(const QVector<KisPaintInformation> &newPaintInfos) {
int now = m_elapsedTimer.elapsed();
int delayedPaintInterval = m_elapsedTimer.elapsed() - m_lastPendingTime;
for (int i = 0; i < newPaintInfos.size(); i++) {
// TODO: Risk of overflowing?
int offsetTime = (delayedPaintInterval * i) / newPaintInfos.size();
m_paintQueue.enqueue(TimedPaintInfo(now + offsetTime, newPaintInfos[i]));
}
m_lastPendingTime = now;
}
void KisStabilizerDelayedPaintHelper::end() {
stabilizerDelayedPaint(true);
m_paintTimer.stop();
}
void KisStabilizerDelayedPaintHelper::cancel() {
m_paintTimer.stop();
m_paintQueue.clear();
}
void KisStabilizerDelayedPaintHelper::stabilizerDelayedPaint(bool isEndStroke) {
if (m_paintQueue.isEmpty()) {
return;
}
if (isEndStroke) {
TimedPaintInfo dequeued = m_paintQueue.dequeue();
while (!m_paintQueue.isEmpty()) {
const TimedPaintInfo dequeued2 = m_paintQueue.dequeue();
m_paintLine(dequeued.paintInfo, dequeued2.paintInfo);
dequeued = dequeued2;
}
m_requestUpdateOutline();
return;
}
int now = m_elapsedTimer.elapsed();
// Always keep one in the queue since painting requires two points
while (m_paintQueue.size() > 1 && m_paintQueue.head().elapsedTime <= now) {
const TimedPaintInfo dequeued = m_paintQueue.dequeue();
m_paintLine(dequeued.paintInfo, m_paintQueue.head().paintInfo);
}
m_requestUpdateOutline();
}
/*
* Copyright (c) 2016 Alvin Wong <alvinhochun-at-gmail-com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_STABILIZER_DELAYED_PAINT_HELPER_H
#define KIS_STABILIZER_DELAYED_PAINT_HELPER_H
#include <QElapsedTimer>
#include <QQueue>
#include <QTimer>
#include <QVector>
#include <functional>
#include "kis_paint_information.h"
#include "kritaui_export.h"
class KRITAUI_EXPORT KisStabilizerDelayedPaintHelper : public QObject
{
Q_OBJECT
struct TimedPaintInfo
{
int elapsedTime;
KisPaintInformation paintInfo;
TimedPaintInfo(int elapsedTime, KisPaintInformation paintInfo);
};
QTimer m_paintTimer;
QQueue<TimedPaintInfo> m_paintQueue;
int m_lastPendingTime;
QElapsedTimer m_elapsedTimer;
// Callbacks
std::function<void(const KisPaintInformation &, const KisPaintInformation &)> m_paintLine;
std::function<void()> m_requestUpdateOutline;
public:
KisStabilizerDelayedPaintHelper();
virtual ~KisStabilizerDelayedPaintHelper() override {}
bool running() const {
return m_paintTimer.isActive();
}
bool hasLastPaintInformation() const {
return !m_paintQueue.isEmpty();
}
KisPaintInformation lastPaintInformation() const {
// Please call hasLastPaintInformation before this
return m_paintQueue.head().paintInfo;
}
void setPaintLineCallback(std::function<void(const KisPaintInformation &, const KisPaintInformation &)> paintLine) {
m_paintLine = paintLine;
}
void setUpdateOutlineCallback(std::function<void()> requestUpdateOutline) {
m_requestUpdateOutline = requestUpdateOutline;
}
void start(int paintInterval, const KisPaintInformation &firstPaintInfo);
void update(const QVector<KisPaintInformation> &newPaintInfos);
void end();
void cancel();
private Q_SLOTS:
void stabilizerDelayedPaint(bool isEndStroke = false);
};
#endif // KIS_STABILIZER_DELAYED_PAINT_HELPER_H
......@@ -36,6 +36,7 @@
#include "kis_update_time_monitor.h"
#include "kis_stabilized_events_sampler.h"
#include "KisStabilizerDelayedPaintHelper.h"
#include "kis_config.h"
......@@ -79,6 +80,7 @@ struct KisToolFreehandHelper::Private
QQueue<KisPaintInformation> stabilizerDeque;
QTimer stabilizerPollTimer;
KisStabilizedEventsSampler stabilizedSampler;
KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper;
int canvasRotation;
bool canvasMirroredH;
......@@ -108,6 +110,15 @@ KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *info
connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
m_d->stabilizerDelayedPaintHelper.setPaintLineCallback(
[this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) {
paintLine(pi1, pi2);
});
m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback(
[this]() {
emit requestExplicitUpdateOutline();
});
}
KisToolFreehandHelper::~KisToolFreehandHelper()
......@@ -138,7 +149,12 @@ QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos
if (!m_d->painterInfos.isEmpty()) {
settings = m_d->resources->currentPaintOpPreset()->settings();
info = m_d->previousPaintInformation;
if (m_d->stabilizerDelayedPaintHelper.running() &&
m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) {
info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation();
} else {
info = m_d->previousPaintInformation;
}
/**
* When LoD mode is active it may happen that the helper has
......@@ -573,6 +589,10 @@ void KisToolFreehandHelper::cancelPaint()
m_d->stabilizerPollTimer.stop();
}
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.cancel();
}
// see a comment in endPaint()
m_d->painterInfos.clear();
......@@ -604,9 +624,15 @@ void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
// Poll and draw regularly
KisConfig cfg;
m_d->stabilizerPollTimer.setInterval(cfg.stabilizerSampleSize());
int stabilizerSampleSize = cfg.stabilizerSampleSize();
m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
m_d->stabilizerPollTimer.start();
int delayedPaintInterval = cfg.stabilizerDelayedPaintInterval();
if (delayedPaintInterval < stabilizerSampleSize) {
m_d->stabilizerDelayedPaintHelper.start(delayedPaintInterval, firstPaintInfo);
}
m_d->stabilizedSampler.clear();
m_d->stabilizedSampler.addEvent(firstPaintInfo);
}
......@@ -652,6 +678,7 @@ void KisToolFreehandHelper::stabilizerPollAndPaint()
KisStabilizedEventsSampler::iterator it;
KisStabilizedEventsSampler::iterator end;
std::tie(it, end) = m_d->stabilizedSampler.range();
QVector<KisPaintInformation> delayedPaintTodoItems;
for (; it != end; ++it) {
KisPaintInformation sampledInfo = *it;
......@@ -672,14 +699,16 @@ void KisToolFreehandHelper::stabilizerPollAndPaint()
KisPaintInformation newInfo =
m_d->getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo);
paintLine(m_d->previousPaintInformation, newInfo);
if (m_d->stabilizerDelayedPaintHelper.running()) {
delayedPaintTodoItems.append(newInfo);
} else {
paintLine(m_d->previousPaintInformation, newInfo);
}
m_d->previousPaintInformation = newInfo;
// Push the new entry through the queue
m_d->stabilizerDeque.dequeue();
m_d->stabilizerDeque.enqueue(sampledInfo);
emit requestExplicitUpdateOutline();
} else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
......@@ -693,6 +722,12 @@ void KisToolFreehandHelper::stabilizerPollAndPaint()
}
m_d->stabilizedSampler.clear();
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
} else {
emit requestExplicitUpdateOutline();
}
}
void KisToolFreehandHelper::stabilizerEnd()
......@@ -709,6 +744,10 @@ void KisToolFreehandHelper::stabilizerEnd()
m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size());
stabilizerPollAndPaint();
}
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.end();
}
}
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment