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>

23
24
#include <KLocale>

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
260
261
    m_d->previousPaintInformation = info;

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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