kis_tool_freehand_helper.cpp 10.3 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
23
24
25
26
27
/*
 *  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>

#include <KoPointerEvent.h>
#include <KoResourceManager.h>

#include "kis_distance_information.h"
#include "kis_painting_information_builder.h"
Dmitry Kazakov's avatar
Dmitry Kazakov committed
28
#include "kis_recording_adapter.h"
29
30
31
32
33
34
35
36
#include "kis_image.h"
#include "strokes/freehand_stroke.h"
#include "kis_painter.h"


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

    bool haveTangent;
    QPointF previousTangent;


    bool hasPaintAtLeastOnce;
    KisDistanceInformation dragDistance;

    QTime strokeTime;
    QTimer strokeTimeoutTimer;

Dmitry Kazakov's avatar
Dmitry Kazakov committed
50
    QVector<KisPainter*> painters;
51
52
53
54
55
56
57
58
59
60
61
62
63
    KisResourcesSnapshotSP resources;
    KisStrokeId strokeId;

    KisPaintInformation previousPaintInformation;
    KisPaintInformation olderPaintInformation;

    bool smooth;
    qreal smoothness;

    QTimer airbrushingTimer;
};


Dmitry Kazakov's avatar
Dmitry Kazakov committed
64
65
KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
                                             KisRecordingAdapter *recordingAdapter)
66
67
68
    : m_d(new Private)
{
    m_d->infoBuilder = infoBuilder;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
69
    m_d->recordingAdapter = recordingAdapter;
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

    m_d->smooth = true;
    m_d->smoothness = 1.0;

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

void KisToolFreehandHelper::setSmoothness(bool smooth, qreal smoothness)
{
    m_d->smooth = smooth;
    m_d->smoothness = smoothness;
}

void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
                                      KoResourceManager *resourceManager,
                                      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->dragDistance.clear();

    m_d->strokeTime.start();

Dmitry Kazakov's avatar
Dmitry Kazakov committed
111
    createPainters(m_d->painters);
112
113
114
115
    m_d->resources = new KisResourcesSnapshot(image,
                                              undoAdapter,
                                              resourceManager);

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
127
    KisStrokeStrategy *stroke =
        new FreehandStrokeStrategy(indirectPainting,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
128
                                   m_d->resources, m_d->painters);
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

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

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

    if(m_d->resources->needsAirbrushing()) {
        m_d->airbrushingTimer.setInterval(m_d->resources->airbrushingRate());
        m_d->airbrushingTimer.start();
    }
}

void KisToolFreehandHelper::paint(KoPointerEvent *event)
{
    KisPaintInformation info =
        m_d->infoBuilder->continueStroke(event,
                                         m_d->previousPaintInformation.pos(),
                                         m_d->strokeTime.elapsed());

    if (m_d->smooth) {
        if (!m_d->haveTangent) {
            m_d->haveTangent = true;
            m_d->previousTangent =
                (info.pos() - m_d->previousPaintInformation.pos()) * m_d->smoothness /
                (3.0 * (info.currentTime() - m_d->previousPaintInformation.currentTime()));
        } else {
            QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) * m_d->smoothness /
                                  (3.0 * (info.currentTime() - m_d->olderPaintInformation.currentTime()));
            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;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
160
161
162
163
164
            paintBezierCurve(m_d->painters,
                             m_d->olderPaintInformation,
                             control1,
                             control2,
                             m_d->previousPaintInformation);
165
166
167
168
169
            m_d->previousTangent = newTangent;
        }
        m_d->olderPaintInformation = m_d->previousPaintInformation;
        m_d->strokeTimeoutTimer.start(100);
    } else {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
170
        paintLine(m_d->painters, m_d->previousPaintInformation, info);
171
172
173
174
175
176
177
178
179
180
181
182
    }

    m_d->previousPaintInformation = info;

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

void KisToolFreehandHelper::endPaint()
{
    if (!m_d->hasPaintAtLeastOnce) {
Dmitry Kazakov's avatar
Dmitry Kazakov committed
183
        paintAt(m_d->painters, m_d->previousPaintInformation);
184
185
186
187
188
189
190
191
192
193
194
195
    } else if (m_d->smooth) {
        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
196
     * Please note that we are not in MT here, so no mutex
197
198
     * is needed
     */
Dmitry Kazakov's avatar
Dmitry Kazakov committed
199
    m_d->painters.clear();
200
201

    m_d->strokesFacade->endStroke(m_d->strokeId);
Dmitry Kazakov's avatar
Dmitry Kazakov committed
202
203
204
205

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->endStroke();
    }
206
207
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
208
209
const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
210
    return !m_d->painters.isEmpty() ? m_d->painters.first()->paintOp() : 0;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
211
212
213
}


214
215
216
217
218
219
220
221
222
void KisToolFreehandHelper::finishStroke()
{
    if(m_d->haveTangent) {
        m_d->haveTangent = false;

        QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) * m_d->smoothness / 3.0;
        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;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
223
224
        paintBezierCurve(m_d->painters,
                         m_d->olderPaintInformation,
225
226
227
228
229
230
231
232
                         control1,
                         control2,
                         m_d->previousPaintInformation);
    }
}

void KisToolFreehandHelper::doAirbrushing()
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
233
234
    if(!m_d->painters.isEmpty()) {
        paintAt(m_d->painters, m_d->previousPaintInformation);
235
236
237
    }
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
238
239
void KisToolFreehandHelper::paintAt(KisPainter *painter,
                                    const KisPaintInformation &pi)
240
241
242
243
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
        new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
Dmitry Kazakov's avatar
Dmitry Kazakov committed
244
                                         painter, pi,
245
                                         m_d->dragDistance));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
246
247
248
249

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addPoint(pi);
    }
250
251
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
252
253
void KisToolFreehandHelper::paintLine(KisPainter *painter,
                                      const KisPaintInformation &pi1,
254
255
256
257
258
                                      const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
        new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
Dmitry Kazakov's avatar
Dmitry Kazakov committed
259
                                         painter, pi1, pi2,
260
                                         m_d->dragDistance));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
261
262
263
264

    if(m_d->recordingAdapter) {
        m_d->recordingAdapter->addLine(pi1, pi2);
    }
265
266
}

Dmitry Kazakov's avatar
Dmitry Kazakov committed
267
268
void KisToolFreehandHelper::paintBezierCurve(KisPainter *painter,
                                             const KisPaintInformation &pi1,
269
270
271
272
273
274
275
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
    m_d->hasPaintAtLeastOnce = true;
    m_d->strokesFacade->addJob(m_d->strokeId,
        new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
Dmitry Kazakov's avatar
Dmitry Kazakov committed
276
                                         painter,
277
278
                                         pi1, control1, control2, pi2,
                                         m_d->dragDistance));
Dmitry Kazakov's avatar
Dmitry Kazakov committed
279
280
281
282

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

Dmitry Kazakov's avatar
Dmitry Kazakov committed
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
void KisToolFreehandHelper::createPainters(QVector<KisPainter*> &painters)
{
    painters << new KisPainter();
}

void KisToolFreehandHelper::paintAt(const QVector<KisPainter*> &painters,
                                    const KisPaintInformation &pi)
{
    paintAt(painters.first(), pi);
}

void KisToolFreehandHelper::paintLine(const QVector<KisPainter*> &painters,
                                      const KisPaintInformation &pi1,
                                      const KisPaintInformation &pi2)
{
    paintLine(painters.first(), pi1, pi2);
}

void KisToolFreehandHelper::paintBezierCurve(const QVector<KisPainter*> &painters,
                                             const KisPaintInformation &pi1,
                                             const QPointF &control1,
                                             const QPointF &control2,
                                             const KisPaintInformation &pi2)
{
    paintBezierCurve(painters.first(), pi1, control1, control2, pi2);
}