kis_tool_freehand_helper.cpp 17 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *  Copyright (c) 2011 Dmitry Kazakov <dimula73@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 "kis_tool_freehand_helper.h"

#include <QTimer>

Halla Rempt's avatar
Halla Rempt committed
23
#include <klocale.h>
24

25
#include <KoPointerEvent.h>
26
#include <KoCanvasResourceManager.h>
27
28
29

#include "kis_distance_information.h"
#include "kis_painting_information_builder.h"
Dmitry Kazakov's avatar
Dmitry Kazakov committed
30
#include "kis_recording_adapter.h"
31
32
#include "kis_image.h"
#include "kis_painter.h"
33
#include "kis_smoothing_options.h"
34

35
36
#include <math.h>
#include <qnumeric.h> // for qIsNaN
37

Dmitry Kazakov's avatar
Dmitry Kazakov committed
38
39
40
//#define DEBUG_BEZIER_CURVES


41
42
43
struct KisToolFreehandHelper::Private
{
    KisPaintingInformationBuilder *infoBuilder;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
44
    KisRecordingAdapter *recordingAdapter;
45
46
47
48
49
50
51
52
53
54
    KisStrokesFacade *strokesFacade;

    bool haveTangent;
    QPointF previousTangent;

    bool hasPaintAtLeastOnce;

    QTime strokeTime;
    QTimer strokeTimeoutTimer;

55
    QVector<PainterInfo*> painterInfos;
56
57
58
59
60
61
    KisResourcesSnapshotSP resources;
    KisStrokeId strokeId;

    KisPaintInformation previousPaintInformation;
    KisPaintInformation olderPaintInformation;

62
    KisSmoothingOptions smoothingOptions;
63
64

    QTimer airbrushingTimer;
65
66
67

    QList<KisPaintInformation> history;
    QList<qreal> velocityHistory;
68
69
70
};


Dmitry Kazakov's avatar
Dmitry Kazakov committed
71
72
KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
                                             KisRecordingAdapter *recordingAdapter)
73
74
75
    : m_d(new Private)
{
    m_d->infoBuilder = infoBuilder;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
76
    m_d->recordingAdapter = recordingAdapter;
77
78
79
80
81
82
83
84
85
86
87
88

    m_d->strokeTimeoutTimer.setSingleShot(true);
    connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));

    connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
}

KisToolFreehandHelper::~KisToolFreehandHelper()
{
    delete m_d;
}

89
void KisToolFreehandHelper::setSmoothness(const KisSmoothingOptions &smoothingOptions)
90
{
91
    m_d->smoothingOptions = smoothingOptions;
92
93
94
}

void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
95
                                      KoCanvasResourceManager *resourceManager,
96
97
98
                                      KisImageWSP image,
                                      KisStrokesFacade *strokesFacade,
                                      KisPostExecutionUndoAdapter *undoAdapter,
99
100
                                      KisNodeSP overrideNode,
                                      KisDefaultBoundsBaseSP bounds)
101
102
103
104
105
106
107
108
109
110
111
112
{
    Q_UNUSED(overrideNode);

    m_d->strokesFacade = strokesFacade;

    m_d->haveTangent = false;
    m_d->previousTangent = QPointF();

    m_d->hasPaintAtLeastOnce = false;

    m_d->strokeTime.start();

113
    createPainters(m_d->painterInfos);
114
115
    m_d->resources = new KisResourcesSnapshot(image,
                                              undoAdapter,
116
117
                                              resourceManager,
                                              bounds);
118

Dmitry Kazakov's avatar
Dmitry Kazakov committed
119
120
121
122
    if(overrideNode) {
        m_d->resources->setCurrentNode(overrideNode);
    }

123
124
    bool indirectPainting = m_d->resources->needsIndirectPainting();

Dmitry Kazakov's avatar
Dmitry Kazakov committed
125
126
127
128
    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->startStroke(image, m_d->resources);
    }

129
    KisStrokeStrategy *stroke =
130
131
            new FreehandStrokeStrategy(indirectPainting,
                                       m_d->resources, m_d->painterInfos, i18n("Freehand Stroke"));
132
133
134
135

    m_d->strokeId = m_d->strokesFacade->startStroke(stroke);

    m_d->previousPaintInformation =
136
            m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());
137

138
139
140
141
    m_d->history.clear();
    m_d->history.append(m_d->previousPaintInformation);
    m_d->velocityHistory.clear();
    m_d->velocityHistory.append(std::numeric_limits<qreal>::signaling_NaN());
142

143
144
145
146
147
148
    if(m_d->resources->needsAirbrushing()) {
        m_d->airbrushingTimer.setInterval(m_d->resources->airbrushingRate());
        m_d->airbrushingTimer.start();
    }
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2,
                                               QPointF tangent1, QPointF tangent2)
{
    if (tangent1.isNull() || tangent2.isNull()) return;

    const qreal maxSanePoint = 1e6;

    QPointF controlTarget1;
    QPointF controlTarget2;

    // Shows the direction in which control points go
    QPointF controlDirection1 = pi1.pos() + tangent1;
    QPointF controlDirection2 = pi2.pos() - tangent2;

    // Lines in the direction of the control points
    QLineF line1(pi1.pos(), controlDirection1);
    QLineF line2(pi2.pos(), controlDirection2);

    // Lines to check whether the control points lay on the opposite
    // side of the line
    QLineF line3(controlDirection1, controlDirection2);
    QLineF line4(pi1.pos(), pi2.pos());

    QPointF intersection;
    if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) {
        qreal controlLength = line4.length() / 2;

        line1.setLength(controlLength);
        line2.setLength(controlLength);

        controlTarget1 = line1.p2();
        controlTarget2 = line2.p2();
    } else {
        QLineF::IntersectType type = line1.intersect(line2, &intersection);

        if (type == QLineF::NoIntersection ||
            intersection.manhattanLength() > maxSanePoint) {

            intersection = 0.5 * (pi1.pos() + pi2.pos());
            qDebug() << "WARINING: there is no intersection point "
                     << "in the basic smoothing algoriths";
        }

        controlTarget1 = intersection;
        controlTarget2 = intersection;
    }

    // shows how near to the controlTarget the value raises
    qreal coeff = 0.8;

    qreal velocity1 = QLineF(QPointF(), tangent1).length();
    qreal velocity2 = QLineF(QPointF(), tangent2).length();

    Q_ASSERT(velocity1 > 0);
    Q_ASSERT(velocity2 > 0);

    qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);

    // the controls should not differ more than 50%
    similarity = qMax(similarity, 0.5);

    // when the controls are symmetric, their size should be smaller
    // to avoid corner-like curves
    coeff *= 1 - qMax(0.0, similarity - 0.8);

    Q_ASSERT(coeff > 0);


    QPointF control1;
    QPointF control2;

    if (velocity1 > velocity2) {
        control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
        coeff *= similarity;
        control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
    } else {
        control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
        coeff *= similarity;
        control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
    }

    paintBezierCurve(m_d->painterInfos,
                     pi1,
                     control1,
                     control2,
                     pi2);
}

237
238
239
void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
240
241
242
243
244
245
246
247
            m_d->infoBuilder->continueStroke(event,
                                             m_d->previousPaintInformation.pos(),
                                             m_d->strokeTime.elapsed());

    // Smooth the coordinates out using the history and the velocity. See
    // https://bugs.kde.org/show_bug.cgi?id=281267 and http://www24.atwiki.jp/sigetch_2007/pages/17.html.
    // This is also implemented in gimp, which is where I cribbed the code from.
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING
248
        && m_d->smoothingOptions.smoothnessDistance > 0.0) {
249
250
251
252
253
254
255
256

        m_d->history.append(info);
        m_d->velocityHistory.append(std::numeric_limits<qreal>::signaling_NaN()); // Fake velocity!

        qreal x = 0.0;
        qreal y = 0.0;

        if (m_d->history.size() > 3) {
257
258
            const qreal avg_events_rate = 8; // ms
            const qreal sigma = m_d->smoothingOptions.smoothnessDistance / (3.0 * avg_events_rate); // '3.0' for (3 * sigma) range
259

260
261
            qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
            qreal gaussianWeight2 = sigma * sigma;
262
263
            qreal velocitySum = 0.0;
            qreal scaleSum = 0.0;
264
            qreal baseRate = 0.0;
265
266
267

            Q_ASSERT(m_d->history.size() == m_d->velocityHistory.size());

268
            for (int i = m_d->history.size() - 1; i >= 0; i--) {
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
                qreal rate = 0.0;

                const KisPaintInformation nextInfo = m_d->history.at(i);
                double velocity = m_d->velocityHistory.at(i);

                if (qIsNaN(velocity)) {

                    int previousTime = nextInfo.currentTime();
                    if (i > 0) {
                        previousTime = m_d->history.at(i - 1).currentTime();
                    }

                    int deltaTime = qMax(1, nextInfo.currentTime() - previousTime); // make sure deltaTime > 1
                    velocity = info.movement().norm() / deltaTime;
                    m_d->velocityHistory[i] = velocity;
                }

286
287
288
289
290
291
292
293
294
295
296
297
                qreal pressureGrad = 0.0;
                if (i < m_d->history.size() - 1) {
                    pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();

                    const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions.tailAggressiveness;

                    if (pressureGrad > 0.0 ) {
                        pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure());
                        velocity += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
                    }
                }

298
                if (gaussianWeight2 != 0.0) {
299
                    velocitySum += velocity;
300
301
                    rate = gaussianWeight * exp(-velocitySum * velocitySum / (2 * gaussianWeight2));
                }
302
303
304
305
306
307
308

                if (m_d->history.size() - i == 1) {
                    baseRate = rate;
                } else if (baseRate / rate > 100) {
                    break;
                }

309
310
311
312
313
314
315
316
317
                scaleSum += rate;
                x += rate * nextInfo.pos().x();
                y += rate * nextInfo.pos().y();
            }

            if (scaleSum != 0.0) {
                x /= scaleSum;
                y /= scaleSum;
            }
318

319
            if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
320
                info.setMovement(toKisVector2D(info.pos() - QPointF(x, y)));
321
                info.setPos(QPointF(x, y));
322
                m_d->history.last() = info;
323
324
325
            }
        }
    }
326

327
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::SIMPLE_SMOOTHING
Dmitry Kazakov's avatar
Dmitry Kazakov committed
328
        || m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING)
329
330
    {
        // Now paint between the coordinates, using the bezier curve interpolation
331
332
333
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
            m_d->previousTangent =
334
                    (info.pos() - m_d->previousPaintInformation.pos()) /
Dmitry Kazakov's avatar
Dmitry Kazakov committed
335
                    (info.currentTime() - m_d->previousPaintInformation.currentTime());
336
        } else {
337
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
Dmitry Kazakov's avatar
Dmitry Kazakov committed
338
339
340
341
342
                    (info.currentTime() - m_d->olderPaintInformation.currentTime());

            paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation,
                               m_d->previousTangent, newTangent);

343
344
345
346
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
347
348
    }
    else {
349
        paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
350
351
352
353
354
355
356
357
358
359
360
361
    }

    m_d->previousPaintInformation = info;

    if(m_d->airbrushingTimer.isActive()) {
        m_d->airbrushingTimer.start();
    }
}

void KisToolFreehandHelper::endPaint()
{
    if (!m_d->hasPaintAtLeastOnce) {
362
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
363
    } else if (m_d->smoothingOptions.smoothingType != KisSmoothingOptions::NO_SMOOTHING) {
364
365
366
367
368
369
370
371
372
373
374
        finishStroke();
    }
    m_d->strokeTimeoutTimer.stop();

    if(m_d->airbrushingTimer.isActive()) {
        m_d->airbrushingTimer.stop();
    }

    /**
     * There might be some timer events still pending, so
     * we should cancel them. Use this flag for the purpose.
Dmitry Kazakov's avatar
Dmitry Kazakov committed
375
     * Please note that we are not in MT here, so no mutex
376
377
     * is needed
     */
378
    m_d->painterInfos.clear();
379
380

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
381
382
383
384

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
385
386
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
387
388
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
{
389
    return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
390
391
392
}


393
394
void KisToolFreehandHelper::finishStroke()
{
395
    if (m_d->haveTangent) {
396
397
        m_d->haveTangent = false;

Dmitry Kazakov's avatar
Dmitry Kazakov committed
398
399
400
401
402
403
404
        QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) /
            (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime());

        paintBezierSegment(m_d->olderPaintInformation,
                           m_d->previousPaintInformation,
                           m_d->previousTangent,
                           newTangent);
405
406
407
408
409
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
410
411
    if(!m_d->painterInfos.isEmpty()) {
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
412
413
414
    }
}

415
void KisToolFreehandHelper::paintAt(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
416
                                    const KisPaintInformation &pi)
417
418
419
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
420
421
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
422
423
424
425

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
426
427
}

428
void KisToolFreehandHelper::paintLine(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
429
                                      const KisPaintInformation &pi1,
430
431
432
433
                                      const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
434
435
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi1, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
436
437
438
439

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
440
441
}

442
void KisToolFreehandHelper::paintBezierCurve(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
443
                                             const KisPaintInformation &pi1,
444
445
446
447
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#ifdef DEBUG_BEZIER_CURVES
    KisPaintInformation tpi1;
    KisPaintInformation tpi2;

    tpi1 = pi1;
    tpi2 = pi2;

    tpi1.setPressure(0.3);
    tpi2.setPressure(0.3);

    paintLine(m_d->painterInfos, tpi1, tpi2);

    tpi1.setPressure(0.6);
    tpi2.setPressure(0.3);

    tpi1.setPos(pi1.pos());
    tpi2.setPos(control1);
    paintLine(m_d->painterInfos, tpi1, tpi2);

    tpi1.setPos(pi2.pos());
    tpi2.setPos(control2);
    paintLine(m_d->painterInfos, tpi1, tpi2);
#endif

472
473
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
474
475
476
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo,
                                                                pi1, control1, control2, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
477
478
479
480

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addCurve(pi1, control1, control2, pi2);
    }
481
482
}

483
void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
484
{
485
    painterInfos << new PainterInfo(new KisPainter(), new KisDistanceInformation());
Dmitry Kazakov's avatar
Dmitry Kazakov committed
486
487
}

488
void KisToolFreehandHelper::paintAt(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
489
490
                                    const KisPaintInformation &pi)
{
491
    paintAt(painterInfos.first(), pi);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
492
493
}

494
void KisToolFreehandHelper::paintLine(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
495
496
497
                                      const KisPaintInformation &pi1,
                                      const KisPaintInformation &pi2)
{
498
    paintLine(painterInfos.first(), pi1, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
499
500
}

501
void KisToolFreehandHelper::paintBezierCurve(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
502
503
504
505
506
                                             const KisPaintInformation &pi1,
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
507
    paintBezierCurve(painterInfos.first(), pi1, control1, control2, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
508
509
}