kis_tool_freehand_helper.cpp 13.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
38
39
40

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

    bool haveTangent;
    QPointF previousTangent;

    bool hasPaintAtLeastOnce;

    QTime strokeTime;
    QTimer strokeTimeoutTimer;

52
    QVector<PainterInfo*> painterInfos;
53
54
55
56
57
58
    KisResourcesSnapshotSP resources;
    KisStrokeId strokeId;

    KisPaintInformation previousPaintInformation;
    KisPaintInformation olderPaintInformation;

59
    KisSmoothingOptions smoothingOptions;
60
61

    QTimer airbrushingTimer;
62
63
64

    QList<KisPaintInformation> history;
    QList<qreal> velocityHistory;
65
66
67
};


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

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

86
void KisToolFreehandHelper::setSmoothness(const KisSmoothingOptions &smoothingOptions)
87
{
88
    m_d->smoothingOptions = smoothingOptions;
89
90
91
}

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

    m_d->strokesFacade = strokesFacade;

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

    m_d->hasPaintAtLeastOnce = false;

    m_d->strokeTime.start();

109
    createPainters(m_d->painterInfos);
110
111
112
113
    m_d->resources = new KisResourcesSnapshot(image,
                                              undoAdapter,
                                              resourceManager);

Dmitry Kazakov's avatar
Dmitry Kazakov committed
114
115
116
117
    if(overrideNode) {
        m_d->resources->setCurrentNode(overrideNode);
    }

118
119
    bool indirectPainting = m_d->resources->needsIndirectPainting();

Dmitry Kazakov's avatar
Dmitry Kazakov committed
120
121
122
123
    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->startStroke(image, m_d->resources);
    }

124
    KisStrokeStrategy *stroke =
125
126
            new FreehandStrokeStrategy(indirectPainting,
                                       m_d->resources, m_d->painterInfos, i18n("Freehand Stroke"));
127
128
129
130

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

    m_d->previousPaintInformation =
131
            m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());
132

133
134
135
136
    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());
137
138
139
140
141
142
143
144
145
    if(m_d->resources->needsAirbrushing()) {
        m_d->airbrushingTimer.setInterval(m_d->resources->airbrushingRate());
        m_d->airbrushingTimer.start();
    }
}

void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
146
147
148
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
            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
            && m_d->smoothingOptions.smoothnessQuality > 1
            && m_d->smoothingOptions.smoothnessFactor > 3.0) {

        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) {

            int length = qMin(m_d->smoothingOptions.smoothnessQuality, m_d->history.size());
            int minIndex = m_d->history.size() - length;

            qreal gaussianWeight = 0.0;
            qreal gaussianWeight2 = m_d->smoothingOptions.smoothnessFactor * m_d->smoothingOptions.smoothnessFactor;
            qreal velocitySum = 0.0;
            qreal scaleSum = 0.0;

            if (gaussianWeight2 != 0.0) {
                gaussianWeight = 1 / (sqrt(2 * M_PI) * m_d->smoothingOptions.smoothnessFactor);
            }

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

            for (int i = m_d->history.size() - 1; i >= minIndex; i--) {
                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;
                }

                if (gaussianWeight2 != 0.0) {
                    velocitySum += velocity * 100;
                    rate = gaussianWeight * exp(-velocitySum * velocitySum / (2 * gaussianWeight2));
                }
                scaleSum += rate;
                x += rate * nextInfo.pos().x();
                y += rate * nextInfo.pos().y();
            }

            if (scaleSum != 0.0) {
                x /= scaleSum;
                y /= scaleSum;
            }
            if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
                m_d->history.last().setPos(QPointF(x, y));
                info.setPos(QPointF(x, y));
            }
        }
    }
216

217
218
219
220
    if (m_d->smoothingOptions.smoothingType == KisSmoothingOptions::SIMPLE_SMOOTHING
            || m_d->smoothingOptions.smoothingType == KisSmoothingOptions::WEIGHTED_SMOOTHING)
    {
        // Now paint between the coordinates, using the bezier curve interpolation
221
222
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
223
224
225
226

            // XXX: 3.0 is a magic number I don't know anything about
            //      1.0 was the old default value for smoothness, and anything lower than that
            //      gave horrible results, so remove that setting.
227
            m_d->previousTangent =
228
229
                    (info.pos() - m_d->previousPaintInformation.pos()) /
                    (3.0 * (info.currentTime() - m_d->previousPaintInformation.currentTime()));
230
        } else {
231
232
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
                    (3.0 * (info.currentTime() - m_d->olderPaintInformation.currentTime()));
233
234
235
            qreal scaleFactor = (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime());
            QPointF control1 = m_d->olderPaintInformation.pos() + m_d->previousTangent * scaleFactor;
            QPointF control2 = m_d->previousPaintInformation.pos() - newTangent * scaleFactor;
236
            paintBezierCurve(m_d->painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
237
238
239
240
                             m_d->olderPaintInformation,
                             control1,
                             control2,
                             m_d->previousPaintInformation);
241
242
243
244
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
245
246
    }
    else {
247
        paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
248
249
250
251
252
253
254
255
256
257
258
259
    }

    m_d->previousPaintInformation = info;

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

void KisToolFreehandHelper::endPaint()
{
    if (!m_d->hasPaintAtLeastOnce) {
260
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
261
    } else if (m_d->smoothingOptions.smoothingType != KisSmoothingOptions::NO_SMOOTHING) {
262
263
264
265
266
267
268
269
270
271
272
        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
273
     * Please note that we are not in MT here, so no mutex
274
275
     * is needed
     */
276
    m_d->painterInfos.clear();
277
278

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
279
280
281
282

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
283
284
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
285
286
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
{
287
    return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
288
289
290
}


291
292
void KisToolFreehandHelper::finishStroke()
{
293
    if (m_d->haveTangent) {
294
295
        m_d->haveTangent = false;

296
        QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) / 3.0;
297
298
299
        qreal scaleFactor = (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime());
        QPointF control1 = m_d->olderPaintInformation.pos() + m_d->previousTangent * scaleFactor;
        QPointF control2 = m_d->previousPaintInformation.pos() - newTangent;
300
        paintBezierCurve(m_d->painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
301
                         m_d->olderPaintInformation,
302
303
304
305
306
307
308
309
                         control1,
                         control2,
                         m_d->previousPaintInformation);
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
310
311
    if(!m_d->painterInfos.isEmpty()) {
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
312
313
314
    }
}

315
void KisToolFreehandHelper::paintAt(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
316
                                    const KisPaintInformation &pi)
317
318
319
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
320
321
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
322
323
324
325

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
326
327
}

328
void KisToolFreehandHelper::paintLine(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
329
                                      const KisPaintInformation &pi1,
330
331
332
333
                                      const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
334
335
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo, pi1, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
336
337
338
339

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
340
341
}

342
void KisToolFreehandHelper::paintBezierCurve(PainterInfo *painterInfo,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
343
                                             const KisPaintInformation &pi1,
344
345
346
347
348
349
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
350
351
352
                               new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
                                                                painterInfo,
                                                                pi1, control1, control2, pi2));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
353
354
355
356

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

359
void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos)
Dmitry Kazakov's avatar
Dmitry Kazakov committed
360
{
361
    painterInfos << new PainterInfo(new KisPainter(), new KisDistanceInformation());
Dmitry Kazakov's avatar
Dmitry Kazakov committed
362
363
}

364
void KisToolFreehandHelper::paintAt(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
365
366
                                    const KisPaintInformation &pi)
{
367
    paintAt(painterInfos.first(), pi);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
368
369
}

370
void KisToolFreehandHelper::paintLine(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
371
372
373
                                      const KisPaintInformation &pi1,
                                      const KisPaintInformation &pi2)
{
374
    paintLine(painterInfos.first(), pi1, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
375
376
}

377
void KisToolFreehandHelper::paintBezierCurve(const QVector<PainterInfo*> &painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
378
379
380
381
382
                                             const KisPaintInformation &pi1,
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
383
    paintBezierCurve(painterInfos.first(), pi1, control1, control2, pi2);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
384
385
}