Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_brushop.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
3 * SPDX-FileCopyrightText: 2004-2008 Boudewijn Rempt <boud@valdyas.org>
4 * SPDX-FileCopyrightText: 2004 Clarence Dang <dang@kde.org>
5 * SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
6 * SPDX-FileCopyrightText: 2004 Cyrille Berger <cberger@cberger.net>
7 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12#include "kis_brushop.h"
13
14#include <QRect>
15
16#include <kis_image.h>
17#include <kis_vec.h>
18#include <kis_debug.h>
19
21#include <KoColor.h>
22
23#include <kis_brush.h>
24#include <kis_global.h>
25#include <kis_paint_device.h>
26#include <kis_painter.h>
28#include <kis_lod_transform.h>
30#include "krita_utils.h"
31#include "kis_algebra_2d.h"
33#include <KisDabCacheUtils.h>
34#include <KisRenderedDab.h>
35#include <kis_tool_freehand.h>
36#include "KisBrushOpResources.h"
37
41
42#include <QThread>
43#include "kis_image_config.h"
44#include "kis_wrapped_rect.h"
45
46
49 , m_sizeOption(settings.data())
50 , m_ratioOption(settings.data())
51 , m_rateOption(settings.data())
52 , m_softnessOption(settings.data())
53 , m_lightnessStrengthOption(settings.data())
54 , m_spacingOption(settings.data())
55 , m_scatterOption(settings.data())
56 , m_sharpnessOption(settings.data())
57 , m_rotationOption(settings.data())
58 , m_opacityOption(settings.data(), node)
59 , m_avgSpacing(50)
60 , m_avgNumDabs(50)
61 , m_avgUpdateTimePerDab(50)
62 , m_idealNumRects(KisImageConfig(true).maxNumberOfThreads())
63 , m_minUpdatePeriod(10)
64 , m_maxUpdatePeriod(100)
65{
66 Q_UNUSED(image);
67 Q_ASSERT(settings);
68
69 m_airbrushData.read(settings.data());
71
76
77 m_brush->notifyBrushIsGoingToBeClonedForStroke();
78
79 KisBrushSP baseBrush = m_brush;
80 auto resourcesFactory =
81 [baseBrush, settings, painter] () {
83 new KisBrushOpResources(settings, painter);
84 resources->brush = baseBrush->clone().dynamicCast<KisBrush>();
85
86 return resources;
87 };
88
89
90 m_dabExecutor.reset(
93 resourcesFactory,
97}
98
102
104{
105 if (!painter()->device()) return KisSpacingInformation(1.0);
106
107 KisBrushSP brush = m_brush;
108 Q_ASSERT(brush);
109 if (!brush)
110 return KisSpacingInformation(1.0);
111
112 if (!brush->canPaintFor(info))
113 return KisSpacingInformation(1.0);
114
115 qreal scale = m_sizeOption.apply(info);
116 scale *= KisLodTransform::lodToScale(painter()->device());
117 if (checkSizeTooSmall(scale)) return KisSpacingInformation();
118
119 qreal rotation = m_rotationOption.apply(info);
120 qreal ratio = m_ratioOption.apply(info);
121
122 KisDabShape shape(scale, ratio, rotation);
123 QPointF cursorPos =
125 brush->maskWidth(shape, 0, 0, info),
126 brush->maskHeight(shape, 0, 0, info));
127
128 qreal dabOpacity = OPACITY_OPAQUE_F;
129 qreal dabFlow = OPACITY_OPAQUE_F;
130
131 m_opacityOption.apply(info, &dabOpacity, &dabFlow);
132
133 KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(),
134 cursorPos,
135 shape,
136 info,
139
140 m_dabExecutor->addDab(request, dabOpacity, dabFlow);
141
142
143 KisSpacingInformation spacingInfo =
144 effectiveSpacing(scale, rotation, &m_airbrushData, &m_spacingOption, info);
145
146 // gather statistics about dabs
147 m_avgSpacing(spacingInfo.scalarApprox());
148
149 return spacingInfo;
150}
151
153{
154 // rendering data
157
158 // speed metrics
160 QElapsedTimer dabRenderingTimer;
161
162 // final report
164};
165
166void KisBrushOp::addMirroringJobs(Qt::Orientation direction,
167 QVector<QRect> &rects,
170{
171 KritaUtils::addJobSequential(jobs, nullptr);
172
179 KisFixedPaintDeviceSP prevDabDevice = 0;
180 for (KisRenderedDab &dab : state->dabsQueue) {
181 const bool skipMirrorPixels = prevDabDevice && prevDabDevice == dab.device;
182
184 [state, &dab, direction, skipMirrorPixels] () {
185 state->painter->mirrorDab(direction, &dab, skipMirrorPixels);
186 }
187 );
188
189 prevDabDevice = dab.device;
190 }
191
192 KritaUtils::addJobSequential(jobs, nullptr);
193
194 for (QRect &rc : rects) {
195 state->painter->mirrorRect(direction, &rc);
196
198 [rc, state] () {
199 state->painter->bltFixed(rc, state->dabsQueue);
200 }
201 );
202 }
203
204 state->allDirtyRects.append(rects);
205}
206
208{
209 bool someDabsAreStillInQueue = false;
210 const bool hasPreparedDabsAtStart = m_dabExecutor->hasPreparedDabs();
211
212 if (!m_updateSharedState && hasPreparedDabsAtStart) {
213
216
217 state->painter = painter();
218
219 {
220 const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime();
221 const qreal totalRenderingTimePerDab = dabRenderingTime + m_avgUpdateTimePerDab.rollingMeanSafe();
222
223 // we limit the number of fetched dabs to fit the maximum update period and not
224 // make visual hiccups
225 const int dabsLimit =
226 totalRenderingTimePerDab > 0 ?
227 qMax(10, int(m_maxUpdatePeriod / totalRenderingTimePerDab * m_idealNumRects)) :
228 -1;
229
230 state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring(), dabsLimit, &someDabsAreStillInQueue);
231 }
232
233 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!state->dabsQueue.isEmpty(),
234 std::make_pair(m_currentUpdatePeriod, false));
235
236 const int diameter = m_dabExecutor->averageDabSize();
237 const qreal spacing = m_avgSpacing.rollingMean();
238
239 const int idealNumRects = m_idealNumRects;
240
241 QVector<QRect> rects;
242
243 // wrap the dabs if needed
244 if (painter()->device()->defaultBounds()->wrapAroundMode()) {
258 const QRect wrapRect = painter()->device()->defaultBounds()->imageBorderRect();
259 const WrapAroundAxis wrapAroundModeAxis = painter()->device()->defaultBounds()->wrapAroundModeAxis();
260
261 QList<KisRenderedDab> wrappedDabs;
262
263 Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) {
264 const QVector<QPoint> normalizationOrigins =
265 KisWrappedRect::normalizationOriginsForRect(dab.realBounds(), wrapRect, wrapAroundModeAxis);
266
267 Q_FOREACH(const QPoint &pt, normalizationOrigins) {
268 KisRenderedDab newDab = dab;
269
270 newDab.offset = pt;
271 rects.append(KisWrappedRect::clipToWrapRect(newDab.realBounds(), wrapRect, wrapAroundModeAxis));
272 wrappedDabs.append(newDab);
273 }
274 }
275
276 state->dabsQueue = wrappedDabs;
277
278 } else {
279 // just get all rects
280 Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) {
281 rects.append(dab.realBounds());
282 }
283 }
284
285 // split/merge rects into non-overlapping areas
287 idealNumRects, diameter, spacing);
288
289 state->allDirtyRects = rects;
290
291 Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) {
292 state->dabPoints.append(dab.realBounds().center());
293 }
294
295 state->dabRenderingTimer.start();
296
297 Q_FOREACH (const QRect &rc, rects) {
299 [rc, state] () {
300 state->painter->bltFixed(rc, state->dabsQueue);
301 }
302 );
303 }
304
311 if (state->painter->hasHorizontalMirroring()) {
312 addMirroringJobs(Qt::Horizontal, rects, state, jobs);
313 }
314
315 if (state->painter->hasVerticalMirroring()) {
316 addMirroringJobs(Qt::Vertical, rects, state, jobs);
317 }
318
319 if (state->painter->hasHorizontalMirroring() && state->painter->hasVerticalMirroring()) {
320 addMirroringJobs(Qt::Horizontal, rects, state, jobs);
321 }
322
324 [state, this, someDabsAreStillInQueue] () {
325 Q_FOREACH(const QRect &rc, state->allDirtyRects) {
326 state->painter->addDirtyRect(rc);
327 }
328
329 state->painter->setAverageOpacity(state->dabsQueue.last().averageOpacity);
330
331 const int updateRenderingTime = state->dabRenderingTimer.elapsed();
332 const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime();
333
334 m_avgNumDabs(state->dabsQueue.size());
335
336 const qreal currentUpdateTimePerDab = qreal(updateRenderingTime) / state->dabsQueue.size();
337 m_avgUpdateTimePerDab(currentUpdateTimePerDab);
338
344 const qreal totalRenderingTimePerDab = dabRenderingTime + currentUpdateTimePerDab;
345
346 const int approxDabRenderingTime =
347 qreal(totalRenderingTimePerDab) * m_avgNumDabs.rollingMean() / m_idealNumRects;
348
350 someDabsAreStillInQueue ? m_minUpdatePeriod :
351 qBound(m_minUpdatePeriod, int(1.5 * approxDabRenderingTime), m_maxUpdatePeriod);
352
353
354 { // debug chunk
355// ENTER_FUNCTION() << ppVar(state->allDirtyRects.size()) << ppVar(state->dabsQueue.size()) << ppVar(dabRenderingTime) << ppVar(updateRenderingTime);
356// ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod) << ppVar(someDabsAreStillInQueue);
357 }
358
359 // release all the dab devices
360 state->dabsQueue.clear();
361
362 m_updateSharedState.clear();
363 }
364 );
365 } else if (m_updateSharedState && hasPreparedDabsAtStart) {
366 someDabsAreStillInQueue = true;
367 }
368
369 return std::make_pair(m_currentUpdatePeriod, someDabsAreStillInQueue);
370}
371
373{
374 const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device());
375 qreal rotation = m_rotationOption.apply(info);
376 return effectiveSpacing(scale, rotation, &m_airbrushData, &m_spacingOption, info);
377}
378
383
385{
386 if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) {
387
388 if (!m_lineCacheDevice) {
390 }
391 else {
393 }
394
396 p.setPaintColor(painter()->paintColor());
397 p.drawDDALine(pi1.pos(), pi2.pos());
398
399 QRect rc = m_lineCacheDevice->extent();
400 painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height());
401 //fixes Bug 338011
403 }
404 else {
405 KisPaintOp::paintLine(pi1, pi2, currentDistance);
406 }
407}
const Params2D p
@ SupportsGradientMode
@ SupportsLightnessMode
WrapAroundAxis
const qreal OPACITY_OPAQUE_F
KisPrecisionOption m_precisionOption
KisSpacingInformation effectiveSpacing(qreal scale) const
KisScatterOption m_scatterOption
Definition kis_brushop.h:72
KisSoftnessOption m_softnessOption
Definition kis_brushop.h:69
KisRateOption m_rateOption
Definition kis_brushop.h:68
KisSpacingOption m_spacingOption
Definition kis_brushop.h:71
KisRotationOption m_rotationOption
Definition kis_brushop.h:74
KisRatioOption m_ratioOption
Definition kis_brushop.h:67
QScopedPointer< KisDabRenderingExecutor > m_dabExecutor
Definition kis_brushop.h:80
KisLightnessStrengthOption m_lightnessStrengthOption
Definition kis_brushop.h:70
const int m_idealNumRects
Definition kis_brushop.h:86
KisRollingMeanAccumulatorWrapper m_avgSpacing
Definition kis_brushop.h:82
KisSizeOption m_sizeOption
Definition kis_brushop.h:66
UpdateSharedStateSP m_updateSharedState
Definition kis_brushop.h:60
KisPaintDeviceSP m_lineCacheDevice
Definition kis_brushop.h:78
KisSharpnessOption m_sharpnessOption
Definition kis_brushop.h:73
qreal m_currentUpdatePeriod
Definition kis_brushop.h:81
void addMirroringJobs(Qt::Orientation direction, QVector< QRect > &rects, UpdateSharedStateSP state, QVector< KisRunnableStrokeJobData * > &jobs)
std::pair< int, bool > doAsynchronousUpdate(QVector< KisRunnableStrokeJobData * > &jobs) override
KisSpacingInformation paintAt(const KisPaintInformation &info) override
KisRollingMeanAccumulatorWrapper m_avgNumDabs
Definition kis_brushop.h:83
KisFlowOpacityOption2 m_opacityOption
Definition kis_brushop.h:75
void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override
KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override
const int m_minUpdatePeriod
Definition kis_brushop.h:88
KisRollingMeanAccumulatorWrapper m_avgUpdateTimePerDab
Definition kis_brushop.h:84
KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override
const int m_maxUpdatePeriod
Definition kis_brushop.h:89
~KisBrushOp() override
KisAirbrushOptionData m_airbrushData
Definition kis_brushop.h:64
bool isChecked() const
virtual WrapAroundAxis wrapAroundModeAxis() const =0
virtual QRect imageBorderRect() const
void apply(KisPainter *painter, const KisPaintInformation &info)
static qreal lodToScale(int levelOfDetail)
virtual void clear()
KisPaintDeviceSP createCompositionSourceDevice() const
virtual const KoColorSpace * compositionSourceColorSpace() const
QRect extent() const
KisDefaultBoundsBaseSP defaultBounds() const
const QPointF & pos() const
void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
KisRunnableStrokeJobsInterface * runnableStrokeJobsInterface
KisPaintDeviceSP device
bool hasImprecisePositionOptions() const
void setHasImprecisePositionOptions(bool value)
qreal apply(const KisPaintInformation &info) const
void applyFanCornersInfo(KisPaintOp *op)
QPointF apply(const KisPaintInformation &info, qreal width, qreal height) const
qreal apply(const KisPaintInformation &info) const
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
QSharedPointer< T > toQShared(T *ptr)
KisTimingInformation effectiveTiming(const KisAirbrushOptionData *airbrushOption, const KisRateOption *rateOption, const KisPaintInformation &pi)
QVector< QRect > splitDabsIntoRects(const QVector< QRect > &dabRects, int idealNumRects, int diameter, qreal spacing)
void addJobConcurrent(QVector< Job * > &jobs, Func func)
void addJobSequential(QVector< Job * > &jobs, Func func)
bool read(const KisPropertiesConfiguration *setting)
QList< KisRenderedDab > dabsQueue
KisPainter * painter
KisFixedPaintDeviceSP dab
KisPaintDeviceSP source() const
virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance)
QRect realBounds() const
static QRect clipToWrapRect(QRect rc, const QRect &wrapRect, WrapAroundAxis wrapAxis)
static QVector< QPoint > normalizationOriginsForRect(const QRect &rc, const QRect &wrapRect, WrapAroundAxis wrapAxis)