kis_tool_freehand_helper.cpp 14 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
                                      KisImageWSP image,
                                      KisStrokesFacade *strokesFacade,
                                      KisPostExecutionUndoAdapter *undoAdapter,
96
97
                                      KisNodeSP overrideNode,
                                      KisDefaultBoundsBaseSP bounds)
98
99
100
101
102
103
104
105
106
107
108
109
{
    Q_UNUSED(overrideNode);

    m_d->strokesFacade = strokesFacade;

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

    m_d->hasPaintAtLeastOnce = false;

    m_d->strokeTime.start();

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

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

120
121
    bool indirectPainting = m_d->resources->needsIndirectPainting();

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

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

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

    m_d->previousPaintInformation =
133
            m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());
134

135
136
137
138
    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());
139

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

void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
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
            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));
            }
        }
    }
219

220
221
222
223
    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
224
225
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
226
227
228
229

            // 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.
230
            m_d->previousTangent =
231
232
                    (info.pos() - m_d->previousPaintInformation.pos()) /
                    (3.0 * (info.currentTime() - m_d->previousPaintInformation.currentTime()));
233
        } else {
234
235
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
                    (3.0 * (info.currentTime() - m_d->olderPaintInformation.currentTime()));
236
237
238
            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;
239
            paintBezierCurve(m_d->painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
240
241
242
243
                             m_d->olderPaintInformation,
                             control1,
                             control2,
                             m_d->previousPaintInformation);
244
245
246
247
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
248
249
    }
    else {
250
        paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
251
252
253
254
255
256
257
258
259
260
261
262
    }

    m_d->previousPaintInformation = info;

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

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

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
282
283
284
285

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
286
287
}

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


294
295
void KisToolFreehandHelper::finishStroke()
{
296
    if (m_d->haveTangent) {
297
298
        m_d->haveTangent = false;

299
        QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) / 3.0;
300
301
302
        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;
303
        paintBezierCurve(m_d->painterInfos,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
304
                         m_d->olderPaintInformation,
305
306
307
308
309
310
311
312
                         control1,
                         control2,
                         m_d->previousPaintInformation);
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
313
314
    if(!m_d->painterInfos.isEmpty()) {
        paintAt(m_d->painterInfos, m_d->previousPaintInformation);
315
316
317
    }
}

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

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
329
330
}

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

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
343
344
}

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

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

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

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

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

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