kis_tool_freehand_helper.cpp 17.8 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
114
115
116
117
118
119
    m_d->previousPaintInformation =
            m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());

    createPainters(m_d->painterInfos,
                   m_d->previousPaintInformation.pos(),
                   m_d->previousPaintInformation.currentTime());

120
121
    m_d->resources = new KisResourcesSnapshot(image,
                                              undoAdapter,
122
123
                                              resourceManager,
                                              bounds);
124

Dmitry Kazakov's avatar
Dmitry Kazakov committed
125
126
127
128
    if(overrideNode) {
        m_d->resources->setCurrentNode(overrideNode);
    }

Dmitry Kazakov's avatar
Dmitry Kazakov committed
129
130
131
132
    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->startStroke(image, m_d->resources);
    }

133
    KisStrokeStrategy *stroke =
134
135
136
        new FreehandStrokeStrategy(m_d->resources->needsIndirectPainting(),
                                   m_d->resources->indirectPaintingCompositeOp(),
                                   m_d->resources, m_d->painterInfos, i18n("Freehand Stroke"));
137
138
139

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

140
141
    m_d->history.clear();
    m_d->velocityHistory.clear();
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
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());
Halla Rempt's avatar
Halla Rempt committed
188
189
//            qDebug() << "WARINING: there is no intersection point "
//                     << "in the basic smoothing algoriths";
Dmitry Kazakov's avatar
Dmitry Kazakov committed
190
191
192
193
194
195
196
197
198
199
200
201
        }

        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();

202
203
204
205
206
    if (velocity1 == 0.0 || velocity2 == 0.0) {
        velocity1 = 1e-6;
        velocity2 = 1e-6;
        qWarning() << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
    }
Dmitry Kazakov's avatar
Dmitry Kazakov committed
207
208
209
210

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

    // the controls should not differ more than 50%
211
    similarity = qMax(similarity, qreal(0.5));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
212
213
214

    // when the controls are symmetric, their size should be smaller
    // to avoid corner-like curves
215
    coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

    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);
}

240
241
242
void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
243
244
245
246
247
248
249
            m_d->infoBuilder->continueStroke(event,
                                             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
250
        && m_d->smoothingOptions.smoothnessDistance > 0.0) {
251

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
        { // initialize current velocity
            QPointF prevPos;
            int prevTime;

            if (!m_d->history.isEmpty()) {
                const KisPaintInformation &prevPi = m_d->history.last();
                prevPos = prevPi.pos();
                prevTime = prevPi.currentTime();
            } else {
                prevPos = m_d->previousPaintInformation.pos();
                prevTime = m_d->previousPaintInformation.currentTime();
            }

            int deltaTime = qMax(1, info.currentTime() - prevTime); // make sure deltaTime > 1
            qreal currentVelocity = QVector2D(info.pos() - prevPos).length() / deltaTime;
            m_d->velocityHistory.append(currentVelocity);
        }

270
271
272
273
274
275
        m_d->history.append(info);

        qreal x = 0.0;
        qreal y = 0.0;

        if (m_d->history.size() > 3) {
276
277
            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
278

279
280
            qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
            qreal gaussianWeight2 = sigma * sigma;
281
282
            qreal velocitySum = 0.0;
            qreal scaleSum = 0.0;
283
            qreal pressure = 0.0;
284
            qreal baseRate = 0.0;
285
286
287

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

288
            for (int i = m_d->history.size() - 1; i >= 0; i--) {
289
290
291
292
                qreal rate = 0.0;

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

295
296
297
298
299
300
301
302
303
304
305
306
                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
                    }
                }

307
                if (gaussianWeight2 != 0.0) {
308
                    velocitySum += velocity;
309
310
                    rate = gaussianWeight * exp(-velocitySum * velocitySum / (2 * gaussianWeight2));
                }
311
312
313
314
315
316
317

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

318
319
320
                scaleSum += rate;
                x += rate * nextInfo.pos().x();
                y += rate * nextInfo.pos().y();
321
322
323
324

                if (m_d->smoothingOptions.smoothPressure) {
                    pressure += rate * nextInfo.pressure();
                }
325
326
327
328
329
            }

            if (scaleSum != 0.0) {
                x /= scaleSum;
                y /= scaleSum;
330
331
332
333

                if (m_d->smoothingOptions.smoothPressure) {
                    pressure /= scaleSum;
                }
334
            }
335

336
337
            if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
                info.setPos(QPointF(x, y));
338
339
340
                if (m_d->smoothingOptions.smoothPressure) {
                    info.setPressure(pressure);
                }
341
                m_d->history.last() = info;
342
343
344
            }
        }
    }
345

346
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::SIMPLE_SMOOTHING
Dmitry Kazakov's avatar
Dmitry Kazakov committed
347
        || m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING)
348
349
    {
        // Now paint between the coordinates, using the bezier curve interpolation
350
351
352
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
            m_d->previousTangent =
353
                    (info.pos() - m_d->previousPaintInformation.pos()) /
Dmitry Kazakov's avatar
Dmitry Kazakov committed
354
                    (info.currentTime() - m_d->previousPaintInformation.currentTime());
355
        } else {
356
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
Dmitry Kazakov's avatar
Dmitry Kazakov committed
357
358
359
360
361
                    (info.currentTime() - m_d->olderPaintInformation.currentTime());

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

362
363
364
365
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
366
367
    }
    else {
368
        paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
369
370
371
372
373
374
375
376
377
378
379
380
    }

    m_d->previousPaintInformation = info;

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

void KisToolFreehandHelper::endPaint()
{
    if (!m_d->hasPaintAtLeastOnce) {
381
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
382
    } else if (m_d->smoothingOptions.smoothingType != KisSmoothingOptions::NO_SMOOTHING) {
383
384
385
386
387
388
389
390
391
392
393
        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
394
     * Please note that we are not in MT here, so no mutex
395
396
     * is needed
     */
397
    m_d->painterInfos.clear();
398
399

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
400
401
402
403

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
404
405
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
406
407
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
{
408
    return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
409
410
411
}


412
413
void KisToolFreehandHelper::finishStroke()
{
414
    if (m_d->haveTangent) {
415
416
        m_d->haveTangent = false;

Dmitry Kazakov's avatar
Dmitry Kazakov committed
417
418
419
420
421
422
423
        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);
424
425
426
427
428
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
429
430
    if(!m_d->painterInfos.isEmpty()) {
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
431
432
433
    }
}

434
void KisToolFreehandHelper::paintAt(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
435
                                    const KisPaintInformation &pi)
436
437
438
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
439
440
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
441
442
443
444

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
445
446
}

447
void KisToolFreehandHelper::paintLine(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
448
                                      const KisPaintInformation &pi1,
449
450
451
452
                                      const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
453
454
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi1, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
455
456
457
458

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
459
460
}

461
void KisToolFreehandHelper::paintBezierCurve(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
462
                                             const KisPaintInformation &pi1,
463
464
465
466
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
#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

491
492
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
493
494
495
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo,
                                                                pi1, control1, control2, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
496
497
498
499

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

502
503
504
void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos,
                                           const QPointF &lastPosition,
                                           int lastTime)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
505
{
506
507
508
    painterInfos <<
        new PainterInfo(new KisPainter(),
                        new KisDistanceInformation(lastPosition, lastTime));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
509
510
}

511
void KisToolFreehandHelper::paintAt(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
512
513
                                    const KisPaintInformation &pi)
{
514
    paintAt(painterInfos.first(), pi);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
515
516
}

517
void KisToolFreehandHelper::paintLine(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
518
519
520
                                      const KisPaintInformation &pi1,
                                      const KisPaintInformation &pi2)
{
521
    paintLine(painterInfos.first(), pi1, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
522
523
}

524
void KisToolFreehandHelper::paintBezierCurve(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
525
526
527
528
529
                                             const KisPaintInformation &pi1,
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
530
    paintBezierCurve(painterInfos.first(), pi1, control1, control2, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
531
532
}