Krita Source Code Documentation
Loading...
Searching...
No Matches
freehand_stroke.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "freehand_stroke.h"
8
9#include <QElapsedTimer>
10#include <QThread>
11#include <QApplication>
12
16#include "kis_painter.h"
17#include "kis_paintop.h"
18
20
25#include <mutex>
26
31
34
68
70 KisFreehandStrokeInfo *strokeInfo,
71 const KUndo2MagicString &name,
72 Flags flags)
73 : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
74 resources, strokeInfo),
75 m_d(new Private(resources))
76{
77 init(flags);
78}
79
82 const KUndo2MagicString &name,
83 Flags flags)
84 : KisPainterBasedStrokeStrategy(QLatin1String("FREEHAND_STROKE"), name,
85 resources, strokeInfos),
86 m_d(new Private(resources))
87{
88 init(flags);
89}
90
92 : KisPainterBasedStrokeStrategy(rhs, levelOfDetail),
93 m_d(new Private(*rhs.m_d))
94{
95 m_d->randomSource.setLevelOfDetail(levelOfDetail);
96}
97
99{
100 KisStrokeSpeedMonitor::instance()->notifyStrokeFinished(m_d->efficiencyMeasurer.averageCursorSpeed(),
101 m_d->efficiencyMeasurer.averageRenderingSpeed(),
102 m_d->efficiencyMeasurer.averageFps(),
103 m_d->resources->currentPaintOpPreset());
104
106}
107
109{
115
117
118 if (m_d->needsAsynchronousUpdates) {
123 setBalancingRatioOverride(0.01); // set priority to updates
124 }
125
127 m_d->efficiencyMeasurer.setEnabled(KisStrokeSpeedMonitor::instance()->haveStrokeSpeedMeasurement());
128}
129
131{
133 m_d->efficiencyMeasurer.notifyRenderingStarted();
134}
135
137{
138 m_d->efficiencyMeasurer.notifyRenderingFinished();
140}
141
142
144{
147
148 // this job is lod-clonable in contrast to FreehandStrokeRunnableJobDataWithUpdate!
149 tryDoUpdate(d->forceUpdate);
150
151 } else if (Data *d = dynamic_cast<Data*>(data)) {
153
155 KisRandomSourceSP rnd = m_d->randomSource.source();
156 KisPerStrokeRandomSourceSP strokeRnd = m_d->randomSource.perStrokeSource();
157
158 switch(d->type) {
159 case Data::POINT:
160 d->pi1.setRandomSource(rnd);
161 d->pi1.setPerStrokeRandomSource(strokeRnd);
162 maskedPainter->paintAt(d->pi1);
163 m_d->efficiencyMeasurer.addSample(d->pi1.pos());
164 break;
165 case Data::LINE:
166 d->pi1.setRandomSource(rnd);
167 d->pi2.setRandomSource(rnd);
168 d->pi1.setPerStrokeRandomSource(strokeRnd);
169 d->pi2.setPerStrokeRandomSource(strokeRnd);
170 maskedPainter->paintLine(d->pi1, d->pi2);
171 m_d->efficiencyMeasurer.addSample(d->pi2.pos());
172 break;
173 case Data::CURVE:
174 d->pi1.setRandomSource(rnd);
175 d->pi2.setRandomSource(rnd);
176 d->pi1.setPerStrokeRandomSource(strokeRnd);
177 d->pi2.setPerStrokeRandomSource(strokeRnd);
179 d->control1,
180 d->control2,
181 d->pi2);
182 m_d->efficiencyMeasurer.addSample(d->pi2.pos());
183 break;
184 case Data::POLYLINE:
185 maskedPainter->paintPolyline(d->points, 0, d->points.size());
186 m_d->efficiencyMeasurer.addSamples(d->points);
187 break;
188 case Data::POLYGON:
189 maskedPainter->paintPolygon(d->points);
190 m_d->efficiencyMeasurer.addSamples(d->points);
191 break;
192 case Data::RECT:
193 maskedPainter->paintRect(d->rect);
194 m_d->efficiencyMeasurer.addSample(d->rect.topLeft());
195 m_d->efficiencyMeasurer.addSample(d->rect.topRight());
196 m_d->efficiencyMeasurer.addSample(d->rect.bottomRight());
197 m_d->efficiencyMeasurer.addSample(d->rect.bottomLeft());
198 break;
199 case Data::ELLIPSE:
200 maskedPainter->paintEllipse(d->rect);
201 // TODO: add speed measures
202 break;
205 // TODO: add speed measures
206 break;
208 maskedPainter->drawPainterPath(d->path, d->pen);
209 break;
211 maskedPainter->drawAndFillPainterPath(d->path, d->pen, d->customColor);
212 break;
213 };
214
215 tryDoUpdate();
216 } else {
218
220 dynamic_cast<FreehandStrokeRunnableJobDataWithUpdate*>(data);
221
222 if (dataWithUpdate) {
223 tryDoUpdate();
224 }
225 }
226}
227
229{
230 // we should enter this function only once!
231 std::unique_lock<std::mutex> entryLock(m_d->updateEntryMutex, std::try_to_lock);
232 if (!entryLock.owns_lock()) return;
233
234 if (m_d->needsAsynchronousUpdates) {
235 if (forceEnd || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod) {
236 m_d->timeSinceLastUpdate.restart();
237
238 for (int i = 0; i < numMaskedPainters(); i++) {
240
241 // TODO: well, we should count all N simultaneous painters for FPS rate!
243
244 bool needsMoreUpdates = false;
245
246 std::tie(m_d->currentUpdatePeriod, needsMoreUpdates) =
248
249 if (!jobs.isEmpty() ||
251 (forceEnd && needsMoreUpdates)) {
252
254 [this] () {
255 this->issueSetDirtySignals();
256 }
257 );
258
259 if (forceEnd && needsMoreUpdates) {
261 [this] () {
262 this->tryDoUpdate(true);
263 }
264 );
265 }
266
267
269 m_d->efficiencyMeasurer.notifyFrameRenderingStarted();
270 }
271
272 }
273 }
274 } else {
276 }
277
278
279}
280
282{
283 QVector<QRect> dirtyRects;
284
285 for (int i = 0; i < numMaskedPainters(); i++) {
287 dirtyRects.append(maskedPainter->takeDirtyRegion());
288 }
289
290 if (needsMaskingUpdates()) {
291
292 // optimize the rects so that they would never intersect with each other!
293 // that is a mandatory step for the multithreaded execution of merging jobs
294
295 // sanity check: updates from the brush should have already been normalized
296 // to the wrapping rect
297 const KisDefaultBoundsBaseSP defaultBounds = targetNode()->projection()->defaultBounds();
298 if (defaultBounds->wrapAroundMode()) {
299 const QRect wrapRect = defaultBounds->imageBorderRect();
300 for (auto it = dirtyRects.begin(); it != dirtyRects.end(); ++it) {
301 KIS_SAFE_ASSERT_RECOVER(wrapRect.contains(*it)) {
302 ENTER_FUNCTION() << ppVar(*it) << ppVar(wrapRect);
303 *it = *it & wrapRect;
304 }
305 }
306 }
307
308 const int maxPatchSizeForMaskingUpdates = 64;
309 const QRect totalRect =
310 std::accumulate(dirtyRects.constBegin(), dirtyRects.constEnd(), QRect(), std::bit_or<QRect>());
311
312 dirtyRects = KisPaintOpUtils::splitAndFilterDabRect(totalRect, dirtyRects, maxPatchSizeForMaskingUpdates);
313
315
317 [this, dirtyRects] () {
318 this->targetNode()->setDirty(dirtyRects);
319 }
320 );
321
323
324 } else {
325 targetNode()->setDirty(dirtyRects);
326 }
327
328 //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
329}
330
332{
333 if (!m_d->resources->presetAllowsLod()) return 0;
334 if (!m_d->resources->currentNode()->supportsLodPainting()) return 0;
335
336 FreehandStrokeStrategy *clone = new FreehandStrokeStrategy(*this, levelOfDetail);
337 return clone;
338}
339
341{
342 m_d->efficiencyMeasurer.notifyCursorMoveStarted();
343}
344
346{
347 m_d->efficiencyMeasurer.notifyCursorMoveFinished();
348}
void doStrokeCallback(KisStrokeJobData *data) override
KisStrokeStrategy * createLodClone(int levelOfDetail) override
void initStrokeCallback() override
const QScopedPointer< Private > m_d
FreehandStrokeStrategy(KisResourcesSnapshotSP resources, KisFreehandStrokeInfo *strokeInfo, const KUndo2MagicString &name, Flags flags=None)
void notifyUserEndedStroke() override
void notifyUserStartedStroke() override
void tryDoUpdate(bool forceEnd=false)
void finishStrokeCallback() override
virtual bool wrapAroundMode() const =0
virtual QRect imageBorderRect() const
std::pair< int, bool > doAsynchronousUpdate(QVector< KisRunnableStrokeJobData * > &jobs)
void paintPolygon(const QVector< QPointF > &points)
void paintPainterPath(const QPainterPath &path)
void paintAt(const KisPaintInformation &pi)
void drawPainterPath(const QPainterPath &path, const QPen &pen)
void paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2)
void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2)
void drawAndFillPainterPath(const QPainterPath &path, const QPen &pen, const KoColor &customColor)
void paintPolyline(const QVector< QPointF > &points, int index=0, int numPoints=-1)
KisDefaultBoundsBaseSP defaultBounds() const
KisMaskedFreehandStrokePainter * maskedPainter(int strokeInfoId)
QVector< KisRunnableStrokeJobData * > doMaskingBrushUpdates(const QVector< QRect > &rects)
KisRunnableStrokeJobsInterface * runnableJobsInterface() const
virtual void addRunnableJobs(const QVector< KisRunnableStrokeJobDataBase * > &list)=0
void enableJob(JobType type, bool enable=true, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
virtual void doStrokeCallback(KisStrokeJobData *data)
static KisStrokeSpeedMonitor * instance()
void notifyStrokeFinished(qreal cursorSpeed, qreal renderingSpeed, qreal fps, KisPaintOpPresetSP preset)
void setBalancingRatioOverride(qreal value)
void setSupportsWrapAroundMode(bool value)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
QVector< QRect > splitAndFilterDabRect(const QRect &totalRect, const QVector< QRect > &dabRects, int idealPatchSize)
void addJobSequential(QVector< Job * > &jobs, Func func)
KisStrokeRandomSource randomSource
KisResourcesSnapshotSP resources
Private(KisResourcesSnapshotSP _resources)
KisStrokeEfficiencyMeasurer efficiencyMeasurer
virtual KisPaintDeviceSP projection() const =0
virtual void setDirty()
Definition kis_node.cpp:577
static KisUpdateTimeMonitor * instance()
void reportPaintOpPreset(KisPaintOpPresetSP preset)