kis_tool_freehand_helper.cpp 18.9 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
#include "kis_paintop_preset.h"

36

37
#include <math.h>
38

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


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

    bool haveTangent;
    QPointF previousTangent;

    bool hasPaintAtLeastOnce;

    QTime strokeTime;
    QTimer strokeTimeoutTimer;

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

    KisPaintInformation previousPaintInformation;
    KisPaintInformation olderPaintInformation;

63
    KisSmoothingOptions smoothingOptions;
64
65

    QTimer airbrushingTimer;
66
67

    QList<KisPaintInformation> history;
68
    QList<qreal> distanceHistory;
69
70

    QPointF lastOutlinePos;
71
72
73
};


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

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

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

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos,
                                                   const KisPaintOpSettings *globalSettings,
                                                   KisPaintOpSettings::OutlineMode mode) const
{
    const KisPaintOpSettings *settings = globalSettings;
    KisPaintInformation info(savedCursorPos);
    KisDistanceInformation distanceInfo(m_d->lastOutlinePos, 0);
    m_d->lastOutlinePos = savedCursorPos;

    if (!m_d->painterInfos.isEmpty()) {
        settings = m_d->resources->currentPaintOpPreset()->settings();
        info = m_d->previousPaintInformation;
        distanceInfo = *m_d->painterInfos.first()->dragDistance;
    }

    KisPaintInformation::DistanceInformaionRegistrar registrar =
        info.registerDistanceInformation(&distanceInfo);

    return settings->brushOutline(info, mode);
}

118
void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
119
                                      KoCanvasResourceManager *resourceManager,
120
121
122
                                      KisImageWSP image,
                                      KisStrokesFacade *strokesFacade,
                                      KisPostExecutionUndoAdapter *undoAdapter,
123
124
                                      KisNodeSP overrideNode,
                                      KisDefaultBoundsBaseSP bounds)
125
126
127
128
129
130
131
132
133
134
135
136
{
    Q_UNUSED(overrideNode);

    m_d->strokesFacade = strokesFacade;

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

    m_d->hasPaintAtLeastOnce = false;

    m_d->strokeTime.start();

137
138
139
140
141
142
143
    m_d->previousPaintInformation =
            m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());

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

144
145
    m_d->resources = new KisResourcesSnapshot(image,
                                              undoAdapter,
146
147
                                              resourceManager,
                                              bounds);
148

Dmitry Kazakov's avatar
Dmitry Kazakov committed
149
150
151
152
    if(overrideNode) {
        m_d->resources->setCurrentNode(overrideNode);
    }

Dmitry Kazakov's avatar
Dmitry Kazakov committed
153
154
155
156
    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->startStroke(image, m_d->resources);
    }

157
    KisStrokeStrategy *stroke =
158
159
160
        new FreehandStrokeStrategy(m_d->resources->needsIndirectPainting(),
                                   m_d->resources->indirectPaintingCompositeOp(),
                                   m_d->resources, m_d->painterInfos, i18n("Freehand Stroke"));
161
162
163

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

164
    m_d->history.clear();
165
    m_d->distanceHistory.clear();
166

167
168
169
170
171
172
    if(m_d->resources->needsAirbrushing()) {
        m_d->airbrushingTimer.setInterval(m_d->resources->airbrushingRate());
        m_d->airbrushingTimer.start();
    }
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
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
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
212
213
//            qDebug() << "WARINING: there is no intersection point "
//                     << "in the basic smoothing algoriths";
Dmitry Kazakov's avatar
Dmitry Kazakov committed
214
215
216
217
218
219
220
221
222
223
224
225
        }

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

226
227
228
229
230
    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
231
232
233
234

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

    // the controls should not differ more than 50%
235
    similarity = qMax(similarity, qreal(0.5));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
236
237
238

    // when the controls are symmetric, their size should be smaller
    // to avoid corner-like curves
239
    coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

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

264
265
266
void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
267
268
269
            m_d->infoBuilder->continueStroke(event,
                                             m_d->strokeTime.elapsed());

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    /**
     * Smooth the coordinates out using the history and the
     * distance. This is a heavily modified version of an algo used in
     * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and
     * http://www24.atwiki.jp/sigetch_2007/pages/17.html.  The main
     * differences are:
     *
     * 1) It uses 'distance' instead of 'velocity', since time
     *    measurements are too unstable in realworld environment
     *
     * 2) There is no 'Quality' parameter, since the number of samples
     *    is calculated automatically
     *
     * 3) 'Tail Aggressiveness' is used for controling the end of the
     *    stroke
     *
     * 4) The formila is a little bit different: 'Distance' parameter
     *    stands for $3 \Sigma$
     */
289
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING
290
        && m_d->smoothingOptions.smoothnessDistance > 0.0) {
291

292
        { // initialize current distance
293
294
295
296
297
298
299
300
301
            QPointF prevPos;

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

302
303
            qreal currentDistance = QVector2D(info.pos() - prevPos).length();
            m_d->distanceHistory.append(currentDistance);
304
305
        }

306
307
308
309
310
311
        m_d->history.append(info);

        qreal x = 0.0;
        qreal y = 0.0;

        if (m_d->history.size() > 3) {
312
            const qreal sigma = m_d->smoothingOptions.smoothnessDistance / 3.0; // '3.0' for (3 * sigma) range
313

314
315
            qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
            qreal gaussianWeight2 = sigma * sigma;
316
            qreal distanceSum = 0.0;
317
            qreal scaleSum = 0.0;
318
            qreal pressure = 0.0;
319
            qreal baseRate = 0.0;
320

321
            Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
322

323
            for (int i = m_d->history.size() - 1; i >= 0; i--) {
324
325
326
                qreal rate = 0.0;

                const KisPaintInformation nextInfo = m_d->history.at(i);
327
328
                double distance = m_d->distanceHistory.at(i);
                Q_ASSERT(distance >= 0.0);
329

330
331
332
333
334
335
336
337
                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());
338
                        distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
339
340
341
                    }
                }

342
                if (gaussianWeight2 != 0.0) {
343
344
                    distanceSum += distance;
                    rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
345
                }
346
347
348
349
350
351
352

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

353
354
355
                scaleSum += rate;
                x += rate * nextInfo.pos().x();
                y += rate * nextInfo.pos().y();
356
357
358
359

                if (m_d->smoothingOptions.smoothPressure) {
                    pressure += rate * nextInfo.pressure();
                }
360
361
362
363
364
            }

            if (scaleSum != 0.0) {
                x /= scaleSum;
                y /= scaleSum;
365
366
367
368

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

371
372
            if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
                info.setPos(QPointF(x, y));
373
374
375
                if (m_d->smoothingOptions.smoothPressure) {
                    info.setPressure(pressure);
                }
376
                m_d->history.last() = info;
377
378
379
            }
        }
    }
380

381
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::SIMPLE_SMOOTHING
Dmitry Kazakov's avatar
Dmitry Kazakov committed
382
        || m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING)
383
384
    {
        // Now paint between the coordinates, using the bezier curve interpolation
385
386
387
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
            m_d->previousTangent =
388
389
                (info.pos() - m_d->previousPaintInformation.pos()) /
                qMax(1, info.currentTime() - m_d->previousPaintInformation.currentTime());
390
        } else {
391
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
392
                qMax(1, info.currentTime() - m_d->olderPaintInformation.currentTime());
Dmitry Kazakov's avatar
Dmitry Kazakov committed
393
394
395
396

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

397
398
399
400
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
401
402
    }
    else {
403
        paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
404
405
406
407
408
409
410
411
412
413
414
415
    }

    m_d->previousPaintInformation = info;

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

void KisToolFreehandHelper::endPaint()
{
    if (!m_d->hasPaintAtLeastOnce) {
416
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
417
    } else if (m_d->smoothingOptions.smoothingType != KisSmoothingOptions::NO_SMOOTHING) {
418
419
420
421
422
423
424
425
426
427
428
        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
429
     * Please note that we are not in MT here, so no mutex
430
431
     * is needed
     */
432
    m_d->painterInfos.clear();
433
434

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
435
436
437
438

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
439
440
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
441
442
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
{
443
    return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
444
445
}

446
447
void KisToolFreehandHelper::finishStroke()
{
448
    if (m_d->haveTangent) {
449
450
        m_d->haveTangent = false;

Dmitry Kazakov's avatar
Dmitry Kazakov committed
451
452
453
454
455
456
457
        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);
458
459
460
461
462
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
463
464
    if(!m_d->painterInfos.isEmpty()) {
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
465
466
467
    }
}

468
void KisToolFreehandHelper::paintAt(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
469
                                    const KisPaintInformation &pi)
470
471
472
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
473
474
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
475
476
477
478

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
479
480
}

481
void KisToolFreehandHelper::paintLine(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
482
                                      const KisPaintInformation &pi1,
483
484
485
486
                                      const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
487
488
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi1, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
489
490
491
492

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
493
494
}

495
void KisToolFreehandHelper::paintBezierCurve(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
496
                                             const KisPaintInformation &pi1,
497
498
499
500
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
#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

525
526
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
527
528
529
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo,
                                                                pi1, control1, control2, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
530
531
532
533

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

536
537
538
void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos,
                                           const QPointF &lastPosition,
                                           int lastTime)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
539
{
540
541
542
    painterInfos <<
        new PainterInfo(new KisPainter(),
                        new KisDistanceInformation(lastPosition, lastTime));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
543
544
}

545
void KisToolFreehandHelper::paintAt(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
546
547
                                    const KisPaintInformation &pi)
{
548
    paintAt(painterInfos.first(), pi);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
549
550
}

551
void KisToolFreehandHelper::paintLine(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
552
553
554
                                      const KisPaintInformation &pi1,
                                      const KisPaintInformation &pi2)
{
555
    paintLine(painterInfos.first(), pi1, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
556
557
}

558
void KisToolFreehandHelper::paintBezierCurve(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
559
560
561
562
563
                                             const KisPaintInformation &pi1,
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
564
    paintBezierCurve(painterInfos.first(), pi1, control1, control2, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
565
566
}